initial import of threaded sql admin plugin
authorDavid Anderson <dvander@alliedmods.net>
Mon Aug 13 22:49:57 2007 +0000 (2007-08-13)
changeset 132284bb2bc248eb
parent 1321 a7c1296ca8d1
child 1323 6137087f0f4b
initial import of threaded sql admin plugin
plugins/admin-sql-threaded.sp
     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 +