1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/plugins/admin-sql-threaded.sp Mon Aug 13 22:49:57 2007 +0000
1.3 @@ -0,0 +1,905 @@
1.4 +/**
1.5 + * admin-sql-threaded.sp
1.6 + * Fetches admins from an SQL database dynamically. Groups and overrides
1.7 + * are fetched once, but also using threads.
1.8 + *
1.9 + * This file is part of SourceMod, Copyright (C) 2004-2007 AlliedModders LLC
1.10 + *
1.11 + * This program is free software; you can redistribute it and/or
1.12 + * modify it under the terms of the GNU General Public License
1.13 + * as published by the Free Software Foundation; either version 2
1.14 + * of the License, or (at your option) any later version.
1.15 + *
1.16 + * This program is distributed in the hope that it will be useful,
1.17 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.18 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.19 + * GNU General Public License for more details.
1.20 + *
1.21 + * You should have received a copy of the GNU General Public License
1.22 + * along with this program; if not, write to the Free Software
1.23 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1.24 + *
1.25 + * Version: $Id$
1.26 + */
1.27 +
1.28 +/* We like semicolons */
1.29 +#pragma semicolon 1
1.30 +
1.31 +#include <sourcemod>
1.32 +
1.33 +public Plugin:myinfo =
1.34 +{
1.35 + name = "SQL Admins (Threaded)",
1.36 + author = "AlliedModders LLC",
1.37 + description = "Reads admins from SQL dynamically",
1.38 + version = SOURCEMOD_VERSION,
1.39 + url = "http://www.sourcemod.net/"
1.40 +};
1.41 +
1.42 +/**
1.43 + * Notes:
1.44 + *
1.45 + * 1) All queries in here are high priority. This is because the admin stuff
1.46 + * is very important. Do not take this to mean that in your script,
1.47 + * everything should be high priority.
1.48 + *
1.49 + * 2) All callbacks are locked with "sequence numbers." This is to make sure
1.50 + * that multiple calls to sm_reloadadmins and the like do not make us
1.51 + * store the results from two or more callbacks accidentally. Instead, we
1.52 + * check the sequence number in each callback with the current "allowed"
1.53 + * sequence number, and if it doesn't match, the callback is cancelled.
1.54 + *
1.55 + * 3) Sequence numbers for groups and overrides are not cleared unless there
1.56 + * was a 100% success in the fetch. This is so we can potentially implement
1.57 + * connection retries in the future.
1.58 + *
1.59 + * 4) Sequence numbers for the admin cache are ignored except for being
1.60 + * non-zero, which means players in-game should be re-checked for admin \
1.61 + * powers.
1.62 + */
1.63 +
1.64 +new Handle:hDatabase = INVALID_HANDLE; /** Database connection */
1.65 +new g_sequence = 0; /** Global unique sequence number */
1.66 +new ConnectLock = 0; /** Connect sequence number */
1.67 +new RebuildCachePart[3] = {0}; /** Cache part sequence numbers */
1.68 +new PlayerSeq[MAXPLAYERS+1]; /** Player-specific sequence numbers */
1.69 +new bool:PlayerAuth[MAXPLAYERS+1]; /** Whether a player has been "pre-authed" */
1.70 +
1.71 +#define _DEBUG
1.72 +
1.73 +public OnMapEnd()
1.74 +{
1.75 + /**
1.76 + * Clean up on map end just so we can start a fresh connection when we need it later.
1.77 + */
1.78 + if (hDatabase != INVALID_HANDLE)
1.79 + {
1.80 + CloseHandle(hDatabase);
1.81 + hDatabase = INVALID_HANDLE;
1.82 + }
1.83 +}
1.84 +
1.85 +public bool:OnClientConnect(client, String:rejectmsg[], maxlen)
1.86 +{
1.87 + PlayerSeq[client] = 0;
1.88 + PlayerAuth[client] = false;
1.89 + return true;
1.90 +}
1.91 +
1.92 +public OnClientDisconnect(client)
1.93 +{
1.94 + PlayerSeq[client] = 0;
1.95 + PlayerAuth[client] = false;
1.96 +}
1.97 +
1.98 +public OnDatabaseConnect(Handle:owner, Handle:hndl, const String:error[], any:data)
1.99 +{
1.100 +#if defined _DEBUG
1.101 + PrintToServer("OnDatabaseConnect(%x,%x,%d) ConnectLock=%d", owner, hndl, data, ConnectLock);
1.102 +#endif
1.103 +
1.104 + /**
1.105 + * If this happens to be an old connection request, ignore it.
1.106 + */
1.107 + if (data != ConnectLock || hDatabase != INVALID_HANDLE)
1.108 + {
1.109 + if (hndl != INVALID_HANDLE)
1.110 + {
1.111 + CloseHandle(hndl);
1.112 + }
1.113 + return;
1.114 + }
1.115 +
1.116 + ConnectLock = 0;
1.117 + hDatabase = hndl;
1.118 +
1.119 + /**
1.120 + * See if the connection is valid. If not, don't un-mark the caches
1.121 + * as needing rebuilding, in case the next connection request works.
1.122 + */
1.123 + if (hDatabase == INVALID_HANDLE)
1.124 + {
1.125 + LogError("Failed to connect to database: %s", error);
1.126 + return;
1.127 + }
1.128 +
1.129 + /**
1.130 + * See if we need to get any of the cache stuff now.
1.131 + */
1.132 + new sequence;
1.133 + if ((sequence = RebuildCachePart[_:AdminCache_Overrides]) != 0)
1.134 + {
1.135 + FetchOverrides(hDatabase, sequence);
1.136 + }
1.137 + if ((sequence = RebuildCachePart[_:AdminCache_Groups]) != 0)
1.138 + {
1.139 + FetchGroups(hDatabase, sequence);
1.140 + }
1.141 + if ((sequence = RebuildCachePart[_:AdminCache_Admins]) != 0)
1.142 + {
1.143 + FetchUsersWeCan(hDatabase);
1.144 + }
1.145 +}
1.146 +
1.147 +RequestDatabaseConnection()
1.148 +{
1.149 + ConnectLock = ++g_sequence;
1.150 + SQL_TConnect(OnDatabaseConnect, "default", ConnectLock);
1.151 +}
1.152 +
1.153 +public OnRebuildAdminCache(AdminCachePart:part)
1.154 +{
1.155 + /**
1.156 + * Mark this part of the cache as being rebuilt. This is used by the
1.157 + * callback system to determine whether the results should still be
1.158 + * used.
1.159 + */
1.160 + new sequence = ++g_sequence;
1.161 + RebuildCachePart[_:part] = sequence;
1.162 +
1.163 + /**
1.164 + * If we don't have a database connection, we can't do any lookups just yet.
1.165 + */
1.166 + if (!hDatabase)
1.167 + {
1.168 + /**
1.169 + * Ask for a new connection if we need it.
1.170 + */
1.171 + if (!ConnectLock)
1.172 + {
1.173 + RequestDatabaseConnection();
1.174 + }
1.175 + return;
1.176 + }
1.177 +
1.178 + if (part == AdminCache_Overrides)
1.179 + {
1.180 + FetchOverrides(hDatabase, sequence);
1.181 + } else if (part == AdminCache_Groups) {
1.182 + FetchGroups(hDatabase, sequence);
1.183 + } else if (part == AdminCache_Admins) {
1.184 + FetchUsersWeCan(hDatabase);
1.185 + }
1.186 +}
1.187 +
1.188 +public Action:OnClientPreAdminCheck(client)
1.189 +{
1.190 + PlayerAuth[client] = true;
1.191 +
1.192 + /**
1.193 + * Play nice with other plugins. If there's no database, don't delay the
1.194 + * connection process. Unfortunately, we can't attempt anything else and
1.195 + * we just have to hope either the database is waiting or someone will type
1.196 + * sm_reloadadmins.
1.197 + */
1.198 + if (hDatabase == INVALID_HANDLE)
1.199 + {
1.200 + return Plugin_Continue;
1.201 + }
1.202 +
1.203 + /**
1.204 + * Similarly, if the cache is in the process of being rebuilt, don't delay
1.205 + * the user's normal connection flow. The database will soon auth the user
1.206 + * normally.
1.207 + */
1.208 + if (RebuildCachePart[_:AdminCache_Admins] != 0)
1.209 + {
1.210 + return Plugin_Continue;
1.211 + }
1.212 +
1.213 + /**
1.214 + * If someone has already assigned an admin ID (bad bad bad), don't
1.215 + * bother waiting.
1.216 + */
1.217 + if (GetUserAdmin(client) != INVALID_ADMIN_ID)
1.218 + {
1.219 + return Plugin_Continue;
1.220 + }
1.221 +
1.222 + FetchUser(hDatabase, client);
1.223 +
1.224 + return Plugin_Handled;
1.225 +}
1.226 +
1.227 +public OnReceiveUserGroups(Handle:owner, Handle:hndl, const String:error[], any:data)
1.228 +{
1.229 + new Handle:pk = Handle:data;
1.230 + ResetPack(pk);
1.231 +
1.232 + new client = ReadPackCell(pk);
1.233 + new sequence = ReadPackCell(pk);
1.234 +
1.235 + /**
1.236 + * Make sure it's the same client.
1.237 + */
1.238 + if (PlayerSeq[client] != sequence)
1.239 + {
1.240 + CloseHandle(pk);
1.241 + return;
1.242 + }
1.243 +
1.244 + new AdminId:adm = AdminId:ReadPackCell(pk);
1.245 +
1.246 + /**
1.247 + * Someone could have sneakily changed the admin id while we waited.
1.248 + */
1.249 + if (GetUserAdmin(client) != adm)
1.250 + {
1.251 + NotifyPostAdminCheck(client);
1.252 + CloseHandle(pk);
1.253 + return;
1.254 + }
1.255 +
1.256 + /**
1.257 + * See if we got results.
1.258 + */
1.259 + if (hndl == INVALID_HANDLE)
1.260 + {
1.261 + decl String:query[255];
1.262 + ReadPackString(pk, query, sizeof(query));
1.263 + LogError("SQL error receiving user: %s", error);
1.264 + LogError("Query dump: %s", query);
1.265 + NotifyPostAdminCheck(client);
1.266 + CloseHandle(pk);
1.267 + return;
1.268 + }
1.269 +
1.270 + decl String:name[80];
1.271 + new GroupId:gid;
1.272 +
1.273 + while (SQL_FetchRow(hndl))
1.274 + {
1.275 + SQL_FetchString(hndl, 0, name, sizeof(name));
1.276 +
1.277 + if ((gid = FindAdmGroup(name)) == INVALID_GROUP_ID)
1.278 + {
1.279 + continue;
1.280 + }
1.281 +
1.282 +#if defined _DEBUG
1.283 + PrintToServer("Binding user group (%d, %d, %d, %s, %d)", client, sequence, adm, name, gid);
1.284 +#endif
1.285 +
1.286 + AdminInheritGroup(adm, gid);
1.287 + }
1.288 +
1.289 + /**
1.290 + * We're DONE! Omg.
1.291 + */
1.292 + NotifyPostAdminCheck(client);
1.293 + CloseHandle(pk);
1.294 +}
1.295 +
1.296 +public OnReceiveUser(Handle:owner, Handle:hndl, const String:error[], any:data)
1.297 +{
1.298 + new Handle:pk = Handle:data;
1.299 + ResetPack(pk);
1.300 +
1.301 + new client = ReadPackCell(pk);
1.302 +
1.303 + /**
1.304 + * Check if this is the latest result request.
1.305 + */
1.306 + new sequence = ReadPackCell(pk);
1.307 + if (PlayerSeq[client] != sequence)
1.308 + {
1.309 + /* Discard everything, since we're out of sequence. */
1.310 + CloseHandle(pk);
1.311 + return;
1.312 + }
1.313 +
1.314 + /**
1.315 + * If we need to use the results, make sure they succeeded.
1.316 + */
1.317 + if (hndl == INVALID_HANDLE)
1.318 + {
1.319 + decl String:query[255];
1.320 + ReadPackString(pk, query, sizeof(query));
1.321 + LogError("SQL error receiving user: %s", error);
1.322 + LogError("Query dump: %s", query);
1.323 + RunAdminCacheChecks(client);
1.324 + NotifyPostAdminCheck(client);
1.325 + CloseHandle(pk);
1.326 + return;
1.327 + }
1.328 +
1.329 + new num_accounts = SQL_GetRowCount(hndl);
1.330 + if (num_accounts == 0)
1.331 + {
1.332 + RunAdminCacheChecks(client);
1.333 + NotifyPostAdminCheck(client);
1.334 + CloseHandle(pk);
1.335 + return;
1.336 + }
1.337 +
1.338 + decl String:authtype[16];
1.339 + decl String:identity[80];
1.340 + decl String:password[80];
1.341 + decl String:flags[32];
1.342 + decl String:name[80];
1.343 + new AdminId:adm, id;
1.344 +
1.345 + /**
1.346 + * Cache user info -- [0] = db id, [1] = cache id, [2] = groups
1.347 + */
1.348 + decl user_lookup[num_accounts][3];
1.349 + new total_users = 0;
1.350 +
1.351 + while (SQL_FetchRow(hndl))
1.352 + {
1.353 + id = SQL_FetchInt(hndl, 0);
1.354 + SQL_FetchString(hndl, 1, authtype, sizeof(authtype));
1.355 + SQL_FetchString(hndl, 2, identity, sizeof(identity));
1.356 + SQL_FetchString(hndl, 3, password, sizeof(password));
1.357 + SQL_FetchString(hndl, 4, flags, sizeof(flags));
1.358 + SQL_FetchString(hndl, 5, name, sizeof(name));
1.359 +
1.360 + /* For dynamic admins we clear anything already in the cache. */
1.361 + if ((adm = FindAdminByIdentity(authtype, identity)) != INVALID_ADMIN_ID)
1.362 + {
1.363 + RemoveAdmin(adm);
1.364 + }
1.365 +
1.366 + adm = CreateAdmin(name);
1.367 + if (!BindAdminIdentity(adm, authtype, identity))
1.368 + {
1.369 + LogError("Could not bind prefetched SQL admin (authtype \"%s\") (identity \"%s\")", authtype, identity);
1.370 + continue;
1.371 + }
1.372 +
1.373 + user_lookup[total_users][0] = id;
1.374 + user_lookup[total_users][1] = _:adm;
1.375 + user_lookup[total_users][2] = SQL_FetchInt(hndl, 6);
1.376 + total_users++;
1.377 +
1.378 +#if defined _DEBUG
1.379 + PrintToServer("Found SQL admin (%d,%s,%s,%s,%s,%s):%d:%d", id, authtype, identity, password, flags, name, adm, user_lookup[total_users-1][2]);
1.380 +#endif
1.381 +
1.382 + /* See if this admin wants a password */
1.383 + if (password[0] != '\0')
1.384 + {
1.385 + SetAdminPassword(adm, password);
1.386 + }
1.387 +
1.388 + /* Apply each flag */
1.389 + new len = strlen(flags);
1.390 + new AdminFlag:flag;
1.391 + for (new i=0; i<len; i++)
1.392 + {
1.393 + if (!FindFlagByChar(flags[i], flag))
1.394 + {
1.395 + continue;
1.396 + }
1.397 + SetAdminFlag(adm, flag, true);
1.398 + }
1.399 + }
1.400 +
1.401 + /**
1.402 + * Try binding the user.
1.403 + */
1.404 + new group_count = 0;
1.405 + RunAdminCacheChecks(client);
1.406 + adm = GetUserAdmin(client);
1.407 + id = 0;
1.408 +
1.409 +
1.410 + for (new i=0; i<total_users; i++)
1.411 + {
1.412 + if (user_lookup[i][1] == _:adm)
1.413 + {
1.414 + id = user_lookup[i][0];
1.415 + group_count = user_lookup[i][2];
1.416 + break;
1.417 + }
1.418 + }
1.419 +
1.420 +#if defined _DEBUG
1.421 + PrintToServer("Binding client (%d, %d) resulted in: (%d, %d, %d)", client, sequence, id, adm, group_count);
1.422 +#endif
1.423 +
1.424 + /**
1.425 + * If we can't verify that we assigned a database admin, or the user has no
1.426 + * groups, don't bother doing anything.
1.427 + */
1.428 + if (!id || !group_count)
1.429 + {
1.430 + NotifyPostAdminCheck(client);
1.431 + CloseHandle(pk);
1.432 + return;
1.433 + }
1.434 +
1.435 + /**
1.436 + * The user has groups -- we need to fetch them!
1.437 + */
1.438 + decl String:query[255];
1.439 + Format(query, sizeof(query), "SELECT g.name FROM sm_admins_groups ag JOIN sm_groups g ON ag.group_id = g.id WHERE ag.admin_id = %d", id);
1.440 +
1.441 + ResetPack(pk);
1.442 + WritePackCell(pk, client);
1.443 + WritePackCell(pk, sequence);
1.444 + WritePackCell(pk, _:adm);
1.445 + WritePackString(pk, query);
1.446 +
1.447 + SQL_TQuery(owner, OnReceiveUserGroups, query, pk, DBPrio_High);
1.448 +}
1.449 +
1.450 +FetchUser(Handle:db, client)
1.451 +{
1.452 + decl String:name[65];
1.453 + decl String:safe_name[140];
1.454 + decl String:steamid[32];
1.455 + decl String:ipaddr[24];
1.456 +
1.457 + /**
1.458 + * Get authentication information from the client.
1.459 + */
1.460 + GetClientName(client, name, sizeof(name));
1.461 + GetClientIP(client, ipaddr, sizeof(ipaddr));
1.462 +
1.463 + steamid[0] = '\0';
1.464 + if (GetClientAuthString(client, steamid, sizeof(steamid)))
1.465 + {
1.466 + if (StrEqual(steamid, "STEAM_ID_LAN"))
1.467 + {
1.468 + steamid[0] = '\0';
1.469 + }
1.470 + }
1.471 +
1.472 + SQL_QuoteString(db, name, safe_name, sizeof(safe_name));
1.473 +
1.474 + /**
1.475 + * Construct the query using the information the user gave us.
1.476 + */
1.477 + decl String:query[512];
1.478 + new len = 0;
1.479 +
1.480 + len += Format(query[len], sizeof(query)-len, "SELECT a.id, a.authtype, a.identity, a.password, a.flags, a.name, COUNT(ag.group_id)");
1.481 + len += Format(query[len], sizeof(query)-len, " FROM sm_admins a LEFT JOIN sm_admins_groups ag ON a.id = ag.admin_id WHERE ");
1.482 + len += Format(query[len], sizeof(query)-len, " (a.authtype = 'ip' AND a.identity = '%s')", ipaddr);
1.483 + len += Format(query[len], sizeof(query)-len, " OR (a.authtype = 'name' AND a.identity = '%s')", safe_name);
1.484 + if (steamid[0] != '\0')
1.485 + {
1.486 + len += Format(query[len], sizeof(query)-len, " OR (a.authtype = 'steam' AND a.identity = '%s')", steamid);
1.487 + }
1.488 + len += Format(query[len], sizeof(query)-len, " GROUP BY a.id");
1.489 +
1.490 + /**
1.491 + * Send the actual query.
1.492 + */
1.493 + PlayerSeq[client] = ++g_sequence;
1.494 +
1.495 + new Handle:pk;
1.496 + pk = CreateDataPack();
1.497 + WritePackCell(pk, client);
1.498 + WritePackCell(pk, PlayerSeq[client]);
1.499 + WritePackString(pk, query);
1.500 +
1.501 +#if defined _DEBUG
1.502 + PrintToServer("Sending user query: %s", query);
1.503 +#endif
1.504 +
1.505 + SQL_TQuery(db, OnReceiveUser, query, pk, DBPrio_High);
1.506 +}
1.507 +
1.508 +FetchUsersWeCan(Handle:db)
1.509 +{
1.510 + new max_clients = GetMaxClients();
1.511 +
1.512 + for (new i=1; i<=max_clients; i++)
1.513 + {
1.514 + if (PlayerAuth[i] && GetUserAdmin(i) == INVALID_ADMIN_ID)
1.515 + {
1.516 + FetchUser(db, i);
1.517 + }
1.518 + }
1.519 +
1.520 + /**
1.521 + * This round of updates is done. Go in peace.
1.522 + */
1.523 + RebuildCachePart[_:AdminCache_Admins] = 0;
1.524 +}
1.525 +
1.526 +public OnReceiveGroupOverrides(Handle:owner, Handle:hndl, const String:error[], any:data)
1.527 +{
1.528 + new Handle:pk = Handle:data;
1.529 + ResetPack(pk);
1.530 +
1.531 + /**
1.532 + * Check if this is the latest result request.
1.533 + */
1.534 + new sequence = ReadPackCell(pk);
1.535 + if (RebuildCachePart[_:AdminCache_Groups] != sequence)
1.536 + {
1.537 + /* Discard everything, since we're out of sequence. */
1.538 + CloseHandle(pk);
1.539 + return;
1.540 + }
1.541 +
1.542 + /**
1.543 + * If we need to use the results, make sure they succeeded.
1.544 + */
1.545 + if (hndl == INVALID_HANDLE)
1.546 + {
1.547 + decl String:query[255];
1.548 + ReadPackString(pk, query, sizeof(query));
1.549 + LogError("SQL error receiving group overrides: %s", error);
1.550 + LogError("Query dump: %s", query);
1.551 + CloseHandle(pk);
1.552 + return;
1.553 + }
1.554 +
1.555 + /* We're done with the pack forever. */
1.556 + CloseHandle(pk);
1.557 +
1.558 + /**
1.559 + * Fetch the overrides.
1.560 + */
1.561 + decl String:name[80];
1.562 + decl String:type[16];
1.563 + decl String:command[64];
1.564 + decl String:access[16];
1.565 + new GroupId:gid;
1.566 + while (SQL_FetchRow(hndl))
1.567 + {
1.568 + SQL_FetchString(hndl, 0, name, sizeof(name));
1.569 + SQL_FetchString(hndl, 1, type, sizeof(type));
1.570 + SQL_FetchString(hndl, 2, command, sizeof(command));
1.571 + SQL_FetchString(hndl, 3, access, sizeof(access));
1.572 +
1.573 + /* Find the group. This is actually faster than doing the ID lookup. */
1.574 + if ((gid = FindAdmGroup(name)) == INVALID_GROUP_ID)
1.575 + {
1.576 + /* Oh well, just ignore it. */
1.577 + continue;
1.578 + }
1.579 +
1.580 + new OverrideType:o_type = Override_Command;
1.581 + if (StrEqual(type, "group"))
1.582 + {
1.583 + o_type = Override_CommandGroup;
1.584 + }
1.585 +
1.586 + new OverrideRule:o_rule = Command_Deny;
1.587 + if (StrEqual(access, "allow"))
1.588 + {
1.589 + o_rule = Command_Allow;
1.590 + }
1.591 +
1.592 +#if defined _DEBUG
1.593 + PrintToServer("AddAdmGroupCmdOverride(%d, %s, %d, %d)", gid, command, o_type, o_rule);
1.594 +#endif
1.595 +
1.596 + AddAdmGroupCmdOverride(gid, command, o_type, o_rule);
1.597 + }
1.598 +
1.599 + /* Clear the sequence so another connect doesn't refetch */
1.600 + RebuildCachePart[_:AdminCache_Groups] = 0;
1.601 +}
1.602 +
1.603 +public OnReceiveGroups(Handle:owner, Handle:hndl, const String:error[], any:data)
1.604 +{
1.605 + new Handle:pk = Handle:data;
1.606 + ResetPack(pk);
1.607 +
1.608 + /**
1.609 + * Check if this is the latest result request.
1.610 + */
1.611 + new sequence = ReadPackCell(pk);
1.612 + if (RebuildCachePart[_:AdminCache_Groups] != sequence)
1.613 + {
1.614 + /* Discard everything, since we're out of sequence. */
1.615 + CloseHandle(pk);
1.616 + return;
1.617 + }
1.618 +
1.619 + /**
1.620 + * If we need to use the results, make sure they succeeded.
1.621 + */
1.622 + if (hndl == INVALID_HANDLE)
1.623 + {
1.624 + decl String:query[255];
1.625 + ReadPackString(pk, query, sizeof(query));
1.626 + LogError("SQL error receiving groups: %s", error);
1.627 + LogError("Query dump: %s", query);
1.628 + CloseHandle(pk);
1.629 + return;
1.630 + }
1.631 +
1.632 + /* We cache basic group info so we can do reverse lookups */
1.633 + new Handle:groups = CreateArray(3);
1.634 +
1.635 + /**
1.636 + * Now start fetching groups.
1.637 + */
1.638 + decl String:immunity[16];
1.639 + decl String:flags[32];
1.640 + decl String:name[128];
1.641 + decl String:grp_immunity[256];
1.642 + while (SQL_FetchRow(hndl))
1.643 + {
1.644 + new id = SQL_FetchInt(hndl, 0);
1.645 + SQL_FetchString(hndl, 1, immunity, sizeof(immunity));
1.646 + SQL_FetchString(hndl, 2, flags, sizeof(flags));
1.647 + SQL_FetchString(hndl, 3, name, sizeof(name));
1.648 + SQL_FetchString(hndl, 4, grp_immunity, sizeof(grp_immunity));
1.649 +
1.650 +#if defined _DEBUG
1.651 + PrintToServer("Adding group (%d, %s, %s, %s, %s)", id, immunity, flags, name, grp_immunity);
1.652 +#endif
1.653 +
1.654 + /* Find or create the group */
1.655 + new GroupId:gid;
1.656 + if ((gid = FindAdmGroup(name)) == INVALID_GROUP_ID)
1.657 + {
1.658 + gid = CreateAdmGroup(name);
1.659 + }
1.660 +
1.661 + /* Add flags from the database to the group */
1.662 + new num_flag_chars = strlen(flags);
1.663 + for (new i=0; i<num_flag_chars; i++)
1.664 + {
1.665 + decl AdminFlag:flag;
1.666 + if (!FindFlagByChar(flags[i], flag))
1.667 + {
1.668 + continue;
1.669 + }
1.670 + SetAdmGroupAddFlag(gid, flag, true);
1.671 + }
1.672 +
1.673 + /* Set the immunity level this group has */
1.674 + if (StrEqual(immunity, "all"))
1.675 + {
1.676 + SetAdmGroupImmunity(gid, Immunity_Default, true);
1.677 + SetAdmGroupImmunity(gid, Immunity_Global, true);
1.678 + } else if (StrEqual(immunity, "default")) {
1.679 + SetAdmGroupImmunity(gid, Immunity_Default, true);
1.680 + } else if (StrEqual(immunity, "global")) {
1.681 + SetAdmGroupImmunity(gid, Immunity_Global, true);
1.682 + }
1.683 +
1.684 + new Handle:immunity_list = UTIL_ExplodeNumberString(grp_immunity);
1.685 +
1.686 + /* Now, save all this for later */
1.687 + decl savedata[3];
1.688 + savedata[0] = id;
1.689 + savedata[1] = _:gid;
1.690 + savedata[2] = _:immunity_list;
1.691 +
1.692 + PushArrayArray(groups, savedata);
1.693 + }
1.694 +
1.695 + /**
1.696 + * Resolve immunity now in a second pass.
1.697 + */
1.698 + new num_groups = GetArraySize(groups);
1.699 + for (new i=0; i<num_groups; i++)
1.700 + {
1.701 + decl savedata[3];
1.702 + GetArrayArray(groups, i, savedata);
1.703 +
1.704 + new id = savedata[0];
1.705 + new GroupId:gid = GroupId:savedata[1];
1.706 + new Handle:immunity_list = Handle:savedata[2];
1.707 +
1.708 + if (immunity_list != INVALID_HANDLE)
1.709 + {
1.710 + new immunity_count = GetArraySize(immunity_list);
1.711 + for (new j=0; j<immunity_count; j++)
1.712 + {
1.713 + new other_id = GetArrayCell(immunity_list, j);
1.714 +
1.715 + if (other_id == id)
1.716 + {
1.717 + continue;
1.718 + }
1.719 +
1.720 + /* See if we can find this other ID */
1.721 + new GroupId:other_gid;
1.722 + if ((other_gid = UTIL_FindGroupInCache(groups, other_id)) != INVALID_GROUP_ID)
1.723 + {
1.724 +#if defined _DEBUG
1.725 + PrintToServer("SetAdmGroupImmuneFrom(%d, %d)", gid, other_gid);
1.726 +#endif
1.727 + SetAdmGroupImmuneFrom(gid, other_gid);
1.728 + }
1.729 + }
1.730 + /**
1.731 + * We close the Handle but don't zero it in the parent array... not worth
1.732 + * the effort.
1.733 + */
1.734 + CloseHandle(immunity_list);
1.735 + }
1.736 + }
1.737 +
1.738 + CloseHandle(groups);
1.739 +
1.740 + /**
1.741 + * Now we're done with both passes. It's time to get the group override list.
1.742 + */
1.743 + decl String:query[255];
1.744 + Format(query,
1.745 + sizeof(query),
1.746 + "SELECT g.name, og.type, og.name, og.access FROM sm_group_overrides og JOIN sm_groups g ON og.group_id = g.id ORDER BY g.id DESC");
1.747 +
1.748 + ResetPack(pk);
1.749 + WritePackCell(pk, sequence);
1.750 + WritePackString(pk, query);
1.751 +
1.752 + SQL_TQuery(owner, OnReceiveGroupOverrides, query, pk, DBPrio_High);
1.753 +}
1.754 +
1.755 +FetchGroups(Handle:db, sequence)
1.756 +{
1.757 + decl String:query[255];
1.758 + new Handle:pk;
1.759 +
1.760 + Format(query, sizeof(query), "SELECT id, immunity, flags, name, groups_immune FROM sm_groups");
1.761 +
1.762 + pk = CreateDataPack();
1.763 + WritePackCell(pk, sequence);
1.764 + WritePackString(pk, query);
1.765 +
1.766 + SQL_TQuery(db, OnReceiveGroups, query, pk, DBPrio_High);
1.767 +}
1.768 +
1.769 +public OnReceiveOverrides(Handle:owner, Handle:hndl, const String:error[], any:data)
1.770 +{
1.771 + new Handle:pk = Handle:data;
1.772 + ResetPack(pk);
1.773 +
1.774 + /**
1.775 + * Check if this is the latest result request.
1.776 + */
1.777 + new sequence = ReadPackCell(pk);
1.778 + if (RebuildCachePart[_:AdminCache_Overrides] != sequence)
1.779 + {
1.780 + /* Discard everything, since we're out of sequence. */
1.781 + CloseHandle(pk);
1.782 + return;
1.783 + }
1.784 +
1.785 + /**
1.786 + * If we need to use the results, make sure they succeeded.
1.787 + */
1.788 + if (hndl == INVALID_HANDLE)
1.789 + {
1.790 + decl String:query[255];
1.791 + ReadPackString(pk, query, sizeof(query));
1.792 + LogError("SQL error receiving overrides: %s", error);
1.793 + LogError("Query dump: %s", query);
1.794 + CloseHandle(pk);
1.795 + return;
1.796 + }
1.797 +
1.798 + /**
1.799 + * We're done with you, now.
1.800 + */
1.801 + CloseHandle(pk);
1.802 +
1.803 + decl String:type[64];
1.804 + decl String:name[64];
1.805 + decl String:flags[32];
1.806 + new flag_bits;
1.807 + while (SQL_FetchRow(hndl))
1.808 + {
1.809 + SQL_FetchString(hndl, 0, type, sizeof(type));
1.810 + SQL_FetchString(hndl, 1, name, sizeof(name));
1.811 + SQL_FetchString(hndl, 2, flags, sizeof(flags));
1.812 +
1.813 +#if defined _DEBUG
1.814 + PrintToServer("Adding override (%s, %s, %s)", type, name, flags);
1.815 +#endif
1.816 +
1.817 + flag_bits = ReadFlagString(flags);
1.818 + if (StrEqual(type, "command"))
1.819 + {
1.820 + AddCommandOverride(name, Override_Command, flag_bits);
1.821 + } else if (StrEqual(type, "group")) {
1.822 + AddCommandOverride(name, Override_CommandGroup, flag_bits);
1.823 + }
1.824 + }
1.825 +
1.826 + /* Clear the sequence so another connect doesn't refetch */
1.827 + RebuildCachePart[_:AdminCache_Overrides] = 0;
1.828 +}
1.829 +
1.830 +FetchOverrides(Handle:db, sequence)
1.831 +{
1.832 + decl String:query[255];
1.833 + new Handle:pk;
1.834 +
1.835 + Format(query, sizeof(query), "SELECT type, name, flags FROM sm_overrides");
1.836 +
1.837 + pk = CreateDataPack();
1.838 + WritePackCell(pk, sequence);
1.839 + WritePackString(pk, query);
1.840 +
1.841 + SQL_TQuery(db, OnReceiveOverrides, query, pk, DBPrio_High);
1.842 +}
1.843 +
1.844 +/**
1.845 + * Finds an entry in a temporary group list (used by immunity resolver).
1.846 + *
1.847 + * @param group_list Array(3,n) (0=db id, 1=gid, 2=immunity list)
1.848 + * @param id group database id
1.849 + * @return GroupId of the database id or INVALID_GROUP_ID.
1.850 + */
1.851 +stock GroupId:UTIL_FindGroupInCache(Handle:group_list, id)
1.852 +{
1.853 + new size = GetArraySize(group_list);
1.854 +
1.855 + for (new i=0; i<size; i++)
1.856 + {
1.857 + decl data[3];
1.858 + GetArrayArray(group_list, i, data);
1.859 +
1.860 + if (data[0] == id)
1.861 + {
1.862 + return GroupId:data[1];
1.863 + }
1.864 + }
1.865 +
1.866 + return INVALID_GROUP_ID;
1.867 +}
1.868 +
1.869 +/**
1.870 + * Breaks a comma-delimited string into a list of numbers, returning a new
1.871 + * dynamic array.
1.872 + *
1.873 + * @param text The string to split.
1.874 + * @return A new array Handle which must be closed, or
1.875 + * INVALID_HANDLE on no strings found.
1.876 + */
1.877 +stock Handle:UTIL_ExplodeNumberString(const String:text[])
1.878 +{
1.879 + new Handle:array = INVALID_HANDLE;
1.880 + decl String:buffer[32];
1.881 + new reloc_idx, idx;
1.882 +
1.883 + while ((idx = SplitString(text[reloc_idx], ",", buffer, sizeof(buffer))) != -1)
1.884 + {
1.885 + reloc_idx += idx;
1.886 + if (text[reloc_idx] == '\0')
1.887 + {
1.888 + break;
1.889 + }
1.890 + if (array == INVALID_HANDLE)
1.891 + {
1.892 + array = CreateArray();
1.893 + }
1.894 + PushArrayCell(array, StringToInt(buffer));
1.895 + }
1.896 +
1.897 + if (text[reloc_idx] != '\0')
1.898 + {
1.899 + if (array == INVALID_HANDLE)
1.900 + {
1.901 + array = CreateArray();
1.902 + }
1.903 + PushArrayCell(array, StringToInt(text[reloc_idx]));
1.904 + }
1.905 +
1.906 + return array;
1.907 +}
1.908 +