plugins/sql-admin-manager.sp
author Scott Ehlert <ds@alliedmods.net>
Thu Nov 27 02:37:31 2008 -0600 (2008-11-27)
changeset 2429 8131b79039a3
parent 2289 44045ead61cd
child 2435 26d58823f9c8
permissions -rw-r--r--
Added msvc9 project file for sample_ext

Also updated msvc8 project file for compilation against MM:S 1.7
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod SQL Admin Manager Plugin
     5  * Adds/managers admins and groups in an SQL database.
     6  *
     7  * SourceMod (C)2004-2008 AlliedModders LLC.  All rights reserved.
     8  * =============================================================================
     9  *
    10  * This program is free software; you can redistribute it and/or modify it under
    11  * the terms of the GNU General Public License, version 3.0, as published by the
    12  * Free Software Foundation.
    13  * 
    14  * This program is distributed in the hope that it will be useful, but WITHOUT
    15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    17  * details.
    18  *
    19  * You should have received a copy of the GNU General Public License along with
    20  * this program.  If not, see <http://www.gnu.org/licenses/>.
    21  *
    22  * As a special exception, AlliedModders LLC gives you permission to link the
    23  * code of this program (as well as its derivative works) to "Half-Life 2," the
    24  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    25  * by the Valve Corporation.  You must obey the GNU General Public License in
    26  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    27  * this exception to all derivative works.  AlliedModders LLC defines further
    28  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    29  * or <http://www.sourcemod.net/license.php>.
    30  *
    31  * Version: $Id$
    32  */
    33 
    34 /* We like semicolons */
    35 #pragma semicolon 1
    36 
    37 #include <sourcemod>
    38 
    39 #define CURRENT_SCHEMA_VERSION		1409
    40 #define SCHEMA_UPGRADE_1			1409
    41 
    42 new current_version[4] = {1, 0, 0, CURRENT_SCHEMA_VERSION};
    43 
    44 public Plugin:myinfo = 
    45 {
    46 	name = "SQL Admin Manager",
    47 	author = "AlliedModders LLC",
    48 	description = "Manages SQL admins",
    49 	version = SOURCEMOD_VERSION,
    50 	url = "http://www.sourcemod.net/"
    51 };
    52 
    53 public OnPluginStart()
    54 {
    55 	LoadTranslations("common.phrases");
    56 	LoadTranslations("sqladmins.phrases");
    57 
    58 	RegAdminCmd("sm_sql_addadmin", Command_AddAdmin, ADMFLAG_ROOT, "Adds an admin to the SQL database");
    59 	RegAdminCmd("sm_sql_deladmin", Command_DelAdmin, ADMFLAG_ROOT, "Removes an admin from the SQL database");
    60 	RegAdminCmd("sm_sql_addgroup", Command_AddGroup, ADMFLAG_ROOT, "Adds a group to the SQL database");
    61 	RegAdminCmd("sm_sql_delgroup", Command_DelGroup, ADMFLAG_ROOT, "Removes a group from the SQL database");
    62 	RegAdminCmd("sm_sql_setadmingroups", Command_SetAdminGroups, ADMFLAG_ROOT, "Sets an admin's groups in the SQL database");
    63 	RegServerCmd("sm_create_adm_tables", Command_CreateTables);
    64 	RegServerCmd("sm_update_adm_tables", Command_UpdateTables);
    65 }
    66 
    67 Handle:Connect()
    68 {
    69 	decl String:error[255];
    70 	new Handle:db;
    71 	
    72 	if (SQL_CheckConfig("admins"))
    73 	{
    74 		db = SQL_Connect("admins", true, error, sizeof(error));
    75 	} else {
    76 		db = SQL_Connect("default", true, error, sizeof(error));
    77 	}
    78 	
    79 	if (db == INVALID_HANDLE)
    80 	{
    81 		LogError("Could not connect to database: %s", error);
    82 	}
    83 	
    84 	return db;
    85 }
    86 
    87 CreateMySQL(client, Handle:db)
    88 {
    89 	new String:queries[7][] = 
    90 	{
    91 		"CREATE TABLE sm_admins (id int(10) unsigned NOT NULL auto_increment, authtype enum('steam','name','ip') NOT NULL, identity varchar(65) NOT NULL, password varchar(65), flags varchar(30) NOT NULL, name varchar(65) NOT NULL, immunity int(10) unsigned NOT NULL, PRIMARY KEY (id))",
    92 		"CREATE TABLE sm_groups (id int(10) unsigned NOT NULL auto_increment, flags varchar(30) NOT NULL, name varchar(120) NOT NULL, immunity_level int(1) unsigned NOT NULL, PRIMARY KEY (id))",
    93 		"CREATE TABLE sm_group_immunity (group_id int(10) unsigned NOT NULL, other_id int(10) unsigned NOT NULL,  PRIMARY KEY (group_id, other_id))",
    94 		"CREATE TABLE sm_group_overrides (group_id int(10) unsigned NOT NULL, type enum('command','group') NOT NULL, name varchar(32) NOT NULL, access enum('allow','deny') NOT NULL, PRIMARY KEY (group_id, type, name))",
    95 		"CREATE TABLE sm_overrides (type enum('command','group') NOT NULL, name varchar(32) NOT NULL, flags varchar(30) NOT NULL, PRIMARY KEY (type,name))",
    96 		"CREATE TABLE sm_admins_groups (admin_id int(10) unsigned NOT NULL, group_id int(10) unsigned NOT NULL, inherit_order int(10) NOT NULL, PRIMARY KEY (admin_id, group_id))",
    97 		"CREATE TABLE IF NOT EXISTS sm_config (cfg_key varchar(32) NOT NULL, cfg_value varchar(255) NOT NULL, PRIMARY KEY (cfg_key))"
    98 	};
    99 
   100 	for (new i = 0; i < 7; i++)
   101 	{
   102 		if (!DoQuery(client, db, queries[i]))
   103 		{
   104 			return;
   105 		}
   106 	}
   107 
   108 	decl String:query[256];
   109 	Format(query, 
   110 		sizeof(query), 
   111 		"INSERT INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '1.0.0.%d') ON DUPLICATE KEY UPDATE cfg_value = '1.0.0.%d'",
   112 		CURRENT_SCHEMA_VERSION,
   113 		CURRENT_SCHEMA_VERSION);
   114 
   115 	if (!DoQuery(client, db, query))
   116 	{
   117 		return;
   118 	}
   119 
   120 	ReplyToCommand(client, "[SM] Admin tables have been created.");
   121 }
   122 
   123 CreateSQLite(client, Handle:db)
   124 {
   125 	new String:queries[7][] = 
   126 	{
   127 		"CREATE TABLE sm_admins (id INTEGER PRIMARY KEY AUTOINCREMENT, authtype varchar(16) NOT NULL CHECK(authtype IN ('steam', 'ip', 'name')), identity varchar(65) NOT NULL, password varchar(65), flags varchar(30) NOT NULL, name varchar(65) NOT NULL, immunity INTEGER NOT NULL)",
   128 		"CREATE TABLE sm_groups (id INTEGER PRIMARY KEY AUTOINCREMENT, flags varchar(30) NOT NULL, name varchar(120) NOT NULL, immunity_level INTEGER NOT NULL)",
   129 		"CREATE TABLE sm_group_immunity (group_id INTEGER NOT NULL, other_id INTEGER NOT NULL, PRIMARY KEY (group_id, other_id))",
   130 		"CREATE TABLE sm_group_overrides (group_id INTEGER NOT NULL, type varchar(16) NOT NULL CHECK (type IN ('command', 'group')), name varchar(32) NOT NULL, access varchar(16) NOT NULL CHECK (access IN ('allow', 'deny')), PRIMARY KEY (group_id, type, name))",
   131 		"CREATE TABLE sm_overrides (type varchar(16) NOT NULL CHECK (type IN ('command', 'group')), name varchar(32) NOT NULL, flags varchar(30) NOT NULL, PRIMARY KEY (type,name))",
   132 		"CREATE TABLE sm_admins_groups (admin_id INTEGER NOT NULL, group_id INTEGER NOT NULL, inherit_order int(10) NOT NULL, PRIMARY KEY (admin_id, group_id))",
   133 		"CREATE TABLE IF NOT EXISTS sm_config (cfg_key varchar(32) NOT NULL, cfg_value varchar(255) NOT NULL, PRIMARY KEY (cfg_key))"
   134 	};
   135 
   136 	for (new i = 0; i < 7; i++)
   137 	{
   138 		if (!DoQuery(client, db, queries[i]))
   139 		{
   140 			return;
   141 		}
   142 	}
   143 
   144 	decl String:query[256];
   145 	Format(query, 
   146 		sizeof(query), 
   147 		"REPLACE INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '1.0.0.%d')",
   148 		CURRENT_SCHEMA_VERSION);
   149 
   150 	if (!DoQuery(client, db, query))
   151 	{
   152 		return;
   153 	}
   154 
   155 	ReplyToCommand(client, "[SM] Admin tables have been created.");
   156 }
   157 
   158 public Action:Command_CreateTables(args)
   159 {
   160 	new client = 0;
   161 	new Handle:db = Connect();
   162 	if (db == INVALID_HANDLE)
   163 	{
   164 		ReplyToCommand(client, "[SM] %t", "Could not connect to database");
   165 		return Plugin_Handled;
   166 	}
   167 
   168 	new String:ident[16];
   169 	SQL_ReadDriver(db, ident, sizeof(ident));
   170 
   171 	if (strcmp(ident, "mysql") == 0)
   172 	{
   173 		CreateMySQL(client, db);
   174 	} else if (strcmp(ident, "sqlite") == 0) {
   175 		CreateSQLite(client, db);
   176 	} else {
   177 		ReplyToCommand(client, "[SM] Unknown driver type '%s', cannot create tables.", ident);
   178 	}
   179 
   180 	CloseHandle(db);
   181 
   182 	return Plugin_Handled;
   183 }
   184 
   185 bool:GetUpdateVersion(client, Handle:db, versions[4])
   186 {
   187 	decl String:query[256];
   188 	new Handle:hQuery;
   189 
   190 	Format(query, sizeof(query), "SELECT cfg_value FROM sm_config WHERE cfg_key = 'admin_version'");
   191 	if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE)
   192 	{
   193 		DoError(client, db, query, "Version lookup query failed");
   194 		return false;
   195 	}
   196 	if (SQL_FetchRow(hQuery))
   197 	{
   198 		decl String:version_string[255];
   199 		SQL_FetchString(hQuery, 0, version_string, sizeof(version_string));
   200 
   201 		decl String:version_numbers[4][12];
   202 		if (ExplodeString(version_string, ".", version_numbers, 4, 12) == 4)
   203 		{
   204 			for (new i = 0; i < 4; i++)
   205 			{
   206 				versions[i] = StringToInt(version_numbers[i]);
   207 			}
   208 		}
   209 	}
   210 
   211 	CloseHandle(hQuery);
   212 
   213 	if (current_version[3] < versions[3])
   214 	{
   215 		ReplyToCommand(client, "[SM] The database is newer than the expected version.");
   216 		return false;
   217 	}
   218 
   219 	if (current_version[3] == versions[3])
   220 	{
   221 		ReplyToCommand(client, "[SM] Your tables are already up to date.");
   222 		return false;
   223 	}
   224 
   225 
   226 	return true;
   227 }
   228 
   229 UpdateSQLite(client, Handle:db)
   230 {
   231 	decl String:query[512];
   232 	new Handle:hQuery;
   233 
   234 	Format(query, sizeof(query), "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'sm_config'");
   235 	if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE)
   236 	{
   237 		DoError(client, db, query, "Table lookup query failed");
   238 		return;
   239 	}
   240 
   241 	new bool:found = SQL_FetchRow(hQuery);
   242 
   243 	CloseHandle(hQuery);
   244 
   245 	new versions[4];
   246 	if (found)
   247 	{
   248 		if (!GetUpdateVersion(client, db, versions))
   249 		{
   250 			return;
   251 		}
   252 	}
   253 
   254 	/* We only know about one upgrade path right now... 
   255 	 * 0 => 1
   256 	 */
   257 	if (versions[3] < SCHEMA_UPGRADE_1)
   258 	{
   259 		new String:queries[8][] = 
   260 		{
   261 			"ALTER TABLE sm_admins ADD immunity INTEGER DEFAULT 0 NOT NULL",
   262 			"CREATE TABLE _sm_groups_temp (id INTEGER PRIMARY KEY AUTOINCREMENT, flags varchar(30) NOT NULL, name varchar(120) NOT NULL, immunity_level INTEGER DEFAULT 0 NOT NULL)",
   263 			"INSERT INTO _sm_groups_temp (id, flags, name) SELECT id, flags, name FROM sm_groups",
   264 			"UPDATE _sm_groups_temp SET immunity_level = 2 WHERE id IN (SELECT g.id FROM sm_groups g WHERE g.immunity = 'global')",
   265 			"UPDATE _sm_groups_temp SET immunity_level = 1 WHERE id IN (SELECT g.id FROM sm_groups g WHERE g.immunity = 'default')",
   266 			"DROP TABLE sm_groups",
   267 			"ALTER TABLE _sm_groups_temp RENAME TO sm_groups",
   268 			"CREATE TABLE IF NOT EXISTS sm_config (cfg_key varchar(32) NOT NULL, cfg_value varchar(255) NOT NULL, PRIMARY KEY (cfg_key))"
   269 		};
   270 
   271 		for (new i = 0; i < 8; i++)
   272 		{
   273 			if (!DoQuery(client, db, queries[i]))
   274 			{
   275 				return;
   276 			}
   277 		}
   278 
   279 		Format(query, 
   280 			sizeof(query), 
   281 			"REPLACE INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '1.0.0.%d')",
   282 			SCHEMA_UPGRADE_1);
   283 
   284 		if (!DoQuery(client, db, query))
   285 		{
   286 			return;
   287 		}
   288 
   289 		versions[3] = SCHEMA_UPGRADE_1;
   290 	}
   291 
   292 	ReplyToCommand(client, "[SM] Your tables are now up to date.");
   293 }
   294 
   295 UpdateMySQL(client, Handle:db)
   296 {
   297 	decl String:query[512];
   298 	new Handle:hQuery;
   299 	
   300 	Format(query, sizeof(query), "SHOW TABLES");
   301 	if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE)
   302 	{
   303 		DoError(client, db, query, "Table lookup query failed");
   304 		return;
   305 	}
   306 
   307 	decl String:table[64];
   308 	new bool:found = false;
   309 	while (SQL_FetchRow(hQuery))
   310 	{
   311 		SQL_FetchString(hQuery, 0, table, sizeof(table));
   312 		if (strcmp(table, "sm_config") == 0)
   313 		{
   314 			found = true;
   315 		}
   316 	}
   317 	CloseHandle(hQuery);
   318 
   319 	new versions[4];
   320 
   321 	if (found && !GetUpdateVersion(client, db, versions))
   322 	{
   323 		return;
   324 	}
   325 
   326 	/* We only know about one upgrade path right now... 
   327 	 * 0 => 1
   328 	 */
   329 	if (versions[3] < SCHEMA_UPGRADE_1)
   330 	{
   331 		new String:queries[6][] = 
   332 		{
   333 			"CREATE TABLE IF NOT EXISTS sm_config (cfg_key varchar(32) NOT NULL, cfg_value varchar(255) NOT NULL, PRIMARY KEY (cfg_key))",
   334 			"ALTER TABLE sm_admins ADD immunity INT UNSIGNED NOT NULL",
   335 			"ALTER TABLE sm_groups ADD immunity_level INT UNSIGNED NOT NULL",
   336 			"UPDATE sm_groups SET immunity_level = 2 WHERE immunity = 'default'",
   337 			"UPDATE sm_groups SET immunity_level = 1 WHERE immunity = 'global'",
   338 			"ALTER TABLE sm_groups DROP immunity"
   339 		};
   340 
   341 		for (new i = 0; i < 6; i++)
   342 		{
   343 			if (!DoQuery(client, db, queries[i]))
   344 			{
   345 				return;
   346 			}
   347 		}
   348 
   349 		decl String:upgr[48];
   350 		Format(upgr, sizeof(upgr), "1.0.0.%d", SCHEMA_UPGRADE_1);
   351 
   352 		Format(query, sizeof(query), "INSERT INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '%s') ON DUPLICATE KEY UPDATE cfg_value = '%s'", upgr, upgr);
   353 		if (!DoQuery(client, db, query))
   354 		{
   355 			return;
   356 		}
   357 
   358 		versions[3] = SCHEMA_UPGRADE_1;
   359 	}
   360 
   361 	ReplyToCommand(client, "[SM] Your tables are now up to date.");
   362 }
   363 
   364 public Action:Command_UpdateTables(args)
   365 {
   366 	new client = 0;
   367 	new Handle:db = Connect();
   368 	if (db == INVALID_HANDLE)
   369 	{
   370 		ReplyToCommand(client, "[SM] %t", "Could not connect to database");
   371 		return Plugin_Handled;
   372 	}
   373 
   374 	new String:ident[16];
   375 	SQL_ReadDriver(db, ident, sizeof(ident));
   376 
   377 	if (strcmp(ident, "mysql") == 0)
   378 	{
   379 		UpdateMySQL(client, db);
   380 	} else if (strcmp(ident, "sqlite") == 0) {
   381 		UpdateSQLite(client, db);
   382 	} else {
   383 		ReplyToCommand(client, "[SM] Unknown driver type, cannot upgrade.");
   384 	}
   385 
   386 	CloseHandle(db);
   387 
   388 	return Plugin_Handled;
   389 }
   390 
   391 public Action:Command_SetAdminGroups(client, args)
   392 {
   393 	if (args < 2)
   394 	{
   395 		ReplyToCommand(client, "[SM] Usage: sm_sql_setadmingroups <authtype> <identity> [group1] ... [group N]");
   396 		return Plugin_Handled;
   397 	}
   398 	
   399 	decl String:authtype[16];
   400 	GetCmdArg(1, authtype, sizeof(authtype));
   401 	
   402 	if (!StrEqual(authtype, "steam")
   403 		&& !StrEqual(authtype, "ip")
   404 		&& !StrEqual(authtype, "name"))
   405 	{
   406 		ReplyToCommand(client, "[SM] %t", "Invalid authtype");
   407 		return Plugin_Handled;
   408 	}
   409 	
   410 	new Handle:db = Connect();
   411 	if (db == INVALID_HANDLE)
   412 	{
   413 		ReplyToCommand(client, "[SM] %t", "Could not connect to database");
   414 		return Plugin_Handled;
   415 	}
   416 	
   417 	decl String:identity[65];
   418 	decl String:safe_identity[140];
   419 	GetCmdArg(2, identity, sizeof(identity));
   420 	SQL_EscapeString(db, identity, safe_identity, sizeof(safe_identity));
   421 	
   422 	decl String:query[255];
   423 	Format(query, 
   424 		sizeof(query),
   425 		"SELECT id FROM sm_admins WHERE authtype = '%s' AND identity = '%s'",
   426 		authtype,
   427 		safe_identity);
   428 		
   429 	new Handle:hQuery;
   430 	if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE)
   431 	{
   432 		return DoError(client, db, query, "Admin lookup query failed");
   433 	}
   434 	
   435 	if (!SQL_FetchRow(hQuery))
   436 	{
   437 		ReplyToCommand(client, "[SM] %t", "SQL Admin not found");
   438 		CloseHandle(hQuery);
   439 		CloseHandle(db);
   440 		return Plugin_Handled;
   441 	}
   442 	
   443 	new id = SQL_FetchInt(hQuery, 0);
   444 	
   445 	CloseHandle(hQuery);
   446 	
   447 	/**
   448 	 * First delete all of the user's existing groups.
   449 	 */
   450 	Format(query, sizeof(query), "DELETE FROM sm_admins_groups WHERE admin_id = %d", id);
   451 	if (!SQL_FastQuery(db, query))
   452 	{
   453 		return DoError(client, db, query, "Admin group deletion query failed");
   454 	}
   455 	
   456 	if (args < 3)
   457 	{
   458 		ReplyToCommand(client, "[SM] %t", "SQL Admin groups reset");
   459 		CloseHandle(db);
   460 		return Plugin_Handled;
   461 	}
   462 	
   463 	decl String:error[256];
   464 	new Handle:hAddQuery, Handle:hFindQuery;
   465 	
   466 	Format(query, sizeof(query), "SELECT id FROM sm_groups WHERE name = ?");
   467 	if ((hFindQuery = SQL_PrepareQuery(db, query, error, sizeof(error))) == INVALID_HANDLE)
   468 	{
   469 		return DoStmtError(client, db, query, error, "Group search prepare failed");
   470 	}
   471 	
   472 	Format(query, 
   473 		sizeof(query), 
   474 		"INSERT INTO sm_admins_groups (admin_id, group_id, inherit_order) VALUES (%d, ?, ?)",
   475 		id);
   476 	if ((hAddQuery = SQL_PrepareQuery(db, query, error, sizeof(error))) == INVALID_HANDLE)
   477 	{
   478 		CloseHandle(hFindQuery);
   479 		return DoStmtError(client, db, query, error, "Add admin group prepare failed");
   480 	}
   481 	
   482 	decl String:name[80];
   483 	new inherit_order = 0;
   484 	for (new i=3; i<=args; i++)
   485 	{
   486 		GetCmdArg(i, name, sizeof(name));
   487 		
   488 		SQL_BindParamString(hFindQuery, 0, name, false);
   489 		if (!SQL_Execute(hFindQuery) || !SQL_FetchRow(hFindQuery))
   490 		{
   491 			ReplyToCommand(client, "[SM] %t", "SQL Group X not found", name);
   492 		} else {
   493 			new gid = SQL_FetchInt(hFindQuery, 0);
   494 			
   495 			SQL_BindParamInt(hAddQuery, 0, gid);
   496 			SQL_BindParamInt(hAddQuery, 1, ++inherit_order);
   497 			if (!SQL_Execute(hAddQuery))
   498 			{
   499 				ReplyToCommand(client, "[SM] %t", "SQL Group X failed to bind", name);
   500 				inherit_order--;
   501 			}
   502 		}
   503 	}
   504 	
   505 	CloseHandle(hAddQuery);
   506 	CloseHandle(hFindQuery);
   507 	CloseHandle(db);
   508 	
   509 	if (inherit_order == 1)
   510 	{
   511 		ReplyToCommand(client, "[SM] %t", "Added group to user");
   512 	} else if (inherit_order > 1) {
   513 		ReplyToCommand(client, "[SM] %t", "Added groups to user", inherit_order);
   514 	}
   515 	
   516 	return Plugin_Handled;
   517 }
   518 
   519 public Action:Command_DelGroup(client, args)
   520 {
   521 	if (args < 1)
   522 	{
   523 		ReplyToCommand(client, "[SM] Usage: sm_sql_delgroup <name>");
   524 		return Plugin_Handled;
   525 	}
   526 
   527 	new Handle:db = Connect();
   528 	if (db == INVALID_HANDLE)
   529 	{
   530 		ReplyToCommand(client, "[SM] %t", "Could not connect to database");
   531 		return Plugin_Handled;
   532 	}
   533 	
   534 	new len;
   535 	decl String:name[80];
   536 	decl String:safe_name[180];
   537 	GetCmdArgString(name, sizeof(name));
   538 	
   539 	/* Strip quotes in case the user tries to use them */
   540 	len = strlen(name);
   541 	if (len > 1 && (name[0] == '"' && name[len-1] == '"'))
   542 	{
   543 		name[--len] = '\0';
   544 		SQL_EscapeString(db, name[1], safe_name, sizeof(safe_name));
   545 	} else {
   546 		SQL_EscapeString(db, name, safe_name, sizeof(safe_name));
   547 	}
   548 	
   549 	decl String:query[256];
   550 	
   551 	new Handle:hQuery;
   552 	Format(query, sizeof(query), "SELECT id FROM sm_groups WHERE name = '%s'", safe_name);
   553 	if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE)
   554 	{
   555 		return DoError(client, db, query, "Group retrieval query failed");
   556 	}
   557 	
   558 	if (!SQL_FetchRow(hQuery))
   559 	{
   560 		ReplyToCommand(client, "[SM] %t", "SQL Group not found");
   561 		CloseHandle(hQuery);
   562 		CloseHandle(db);
   563 		return Plugin_Handled;
   564 	}
   565 	
   566 	new id = SQL_FetchInt(hQuery, 0);
   567 	
   568 	CloseHandle(hQuery);
   569 	
   570 	/* Delete admin inheritance for this group */
   571 	Format(query, sizeof(query), "DELETE FROM sm_admins_groups WHERE group_id = %d", id);
   572 	if (!SQL_FastQuery(db, query))
   573 	{
   574 		return DoError(client, db, query, "Admin group deletion query failed");
   575 	}
   576 	
   577 	/* Delete group overrides */
   578 	Format(query, sizeof(query), "DELETE FROM sm_group_overrides WHERE group_id = %d", id);
   579 	if (!SQL_FastQuery(db, query))
   580 	{
   581 		return DoError(client, db, query, "Group override deletion query failed");
   582 	}
   583 	
   584 	/* Delete immunity */
   585 	Format(query, sizeof(query), "DELETE FROM sm_group_immunity WHERE group_id = %d OR other_id = %d", id, id);
   586 	if (!SQL_FastQuery(db, query))
   587 	{
   588 		return DoError(client, db, query, "Group immunity deletion query failed");
   589 	}
   590 	
   591 	/* Finally delete the group */
   592 	Format(query, sizeof(query), "DELETE FROM sm_groups WHERE id = %d", id);
   593 	if (!SQL_FastQuery(db, query))
   594 	{
   595 		return DoError(client, db, query, "Group deletion query failed");
   596 	}
   597 	
   598 	ReplyToCommand(client, "[SM] %t", "SQL Group deleted");
   599 	
   600 	CloseHandle(db);
   601 	
   602 	return Plugin_Handled;
   603 }
   604 
   605 public Action:Command_AddGroup(client, args)
   606 {
   607 	if (args < 2)
   608 	{
   609 		ReplyToCommand(client, "[SM] Usage: sm_sql_addgroup <flags> <name> [immunity]");
   610 		return Plugin_Handled;
   611 	}
   612 
   613 	new immunity;
   614 	if (args >= 3)
   615 	{
   616 		new String:arg3[32];
   617 		GetCmdArg(3, arg3, sizeof(arg3));
   618 		if (!StringToIntEx(arg3, immunity))
   619 		{
   620 			ReplyToCommand(client, "[SM] %t", "Invalid immunity");
   621 			return Plugin_Handled;
   622 		}
   623 	}
   624 	
   625 	new Handle:db = Connect();
   626 	if (db == INVALID_HANDLE)
   627 	{
   628 		ReplyToCommand(client, "[SM] %t", "Could not connect to database");
   629 		return Plugin_Handled;
   630 	}
   631 	
   632 	decl String:name[64];
   633 	decl String:safe_name[64];
   634 	GetCmdArg(1, name, sizeof(name));
   635 	SQL_EscapeString(db, name, safe_name, sizeof(safe_name));
   636 	
   637 	new Handle:hQuery;
   638 	decl String:query[256];
   639 	Format(query, sizeof(query), "SELECT id FROM sm_groups WHERE name = '%s'", safe_name);
   640 	if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE)
   641 	{
   642 		return DoError(client, db, query, "Group retrieval query failed");
   643 	}
   644 	
   645 	if (SQL_GetRowCount(hQuery) > 0)
   646 	{
   647 		ReplyToCommand(client, "[SM] %t", "SQL Group already exists");
   648 		CloseHandle(hQuery);
   649 		CloseHandle(db);
   650 		return Plugin_Handled;
   651 	}
   652 	
   653 	CloseHandle(hQuery);
   654 	
   655 	decl String:flags[30];
   656 	decl String:safe_flags[64];
   657 	GetCmdArg(2, flags, sizeof(safe_flags));
   658 	SQL_EscapeString(db, flags, safe_flags, sizeof(safe_flags));
   659 	
   660 	Format(query, 
   661 		sizeof(query),
   662 		"INSERT INTO sm_groups (flags, name, immunity_level) VALUES ('%s', '%s', '%d')",
   663 		safe_name,
   664 		safe_flags,
   665 		immunity);
   666 	
   667 	if (!SQL_FastQuery(db, query))
   668 	{
   669 		return DoError(client, db, query, "Group insertion query failed");
   670 	}
   671 	
   672 	ReplyToCommand(client, "[SM] %t", "SQL Group added");
   673 	
   674 	CloseHandle(db);
   675 		
   676 	return Plugin_Handled;
   677 }	
   678 
   679 public Action:Command_DelAdmin(client, args)
   680 {
   681 	if (args < 2)
   682 	{
   683 		ReplyToCommand(client, "[SM] Usage: sm_sql_deladmin <authtype> <identity>");
   684 		ReplyToCommand(client, "[SM] %t", "Invalid authtype");
   685 		return Plugin_Handled;
   686 	}
   687 	
   688 	decl String:authtype[16];
   689 	GetCmdArg(1, authtype, sizeof(authtype));
   690 	
   691 	if (!StrEqual(authtype, "steam")
   692 		&& !StrEqual(authtype, "ip")
   693 		&& !StrEqual(authtype, "name"))
   694 	{
   695 		ReplyToCommand(client, "[SM] %t", "Invalid authtype");
   696 		return Plugin_Handled;
   697 	}
   698 	
   699 	new Handle:db = Connect();
   700 	if (db == INVALID_HANDLE)
   701 	{
   702 		ReplyToCommand(client, "[SM] %t", "Could not connect to database");
   703 		return Plugin_Handled;
   704 	}
   705 	
   706 	decl String:identity[65];
   707 	decl String:safe_identity[140];
   708 	GetCmdArg(2, identity, sizeof(identity));
   709 	SQL_EscapeString(db, identity, safe_identity, sizeof(safe_identity));
   710 	
   711 	decl String:query[255];
   712 	Format(query, 
   713 		sizeof(query),
   714 		"SELECT id FROM sm_admins WHERE authtype = '%s' AND identity = '%s'",
   715 		authtype,
   716 		safe_identity);
   717 		
   718 	new Handle:hQuery;
   719 	if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE)
   720 	{
   721 		return DoError(client, db, query, "Admin lookup query failed");
   722 	}
   723 	
   724 	if (!SQL_FetchRow(hQuery))
   725 	{
   726 		ReplyToCommand(client, "[SM] %t", "SQL Admin not found");
   727 		CloseHandle(hQuery);
   728 		CloseHandle(db);
   729 		return Plugin_Handled;
   730 	}
   731 	
   732 	new id = SQL_FetchInt(hQuery, 0);
   733 	
   734 	CloseHandle(hQuery);
   735 	
   736 	/* Delete group bindings */
   737 	Format(query, sizeof(query), "DELETE FROM sm_admins_groups WHERE admin_id = %d", id);
   738 	if (!SQL_FastQuery(db, query))
   739 	{
   740 		return DoError(client, db, query, "Admin group deletion query failed");
   741 	}
   742 	
   743 	Format(query, sizeof(query), "DELETE FROM sm_admins WHERE id = %d", id);
   744 	if (!SQL_FastQuery(db, query))
   745 	{
   746 		return DoError(client, db, query, "Admin deletion query failed");
   747 	}
   748 	
   749 	CloseHandle(db);
   750 	
   751 	ReplyToCommand(client, "[SM] %t", "SQL Admin deleted");
   752 	
   753 	return Plugin_Handled;
   754 }
   755 
   756 public Action:Command_AddAdmin(client, args)
   757 {
   758 	if (args < 4)
   759 	{
   760 		ReplyToCommand(client, "[SM] Usage: sm_sql_addadmin <alias> <authtype> <identity> <flags> [immunity] [password]");
   761 		ReplyToCommand(client, "[SM] %t", "Invalid authtype");
   762 		return Plugin_Handled;
   763 	}
   764 	
   765 	decl String:authtype[16];
   766 	GetCmdArg(2, authtype, sizeof(authtype));
   767 	
   768 	if (!StrEqual(authtype, "steam")
   769 		&& !StrEqual(authtype, "ip")
   770 		&& !StrEqual(authtype, "name"))
   771 	{
   772 		ReplyToCommand(client, "[SM] %t", "Invalid authtype");
   773 		return Plugin_Handled;
   774 	}
   775 
   776 	new immunity;
   777 	if (args >= 5)
   778 	{
   779 		new String:arg5[32];
   780 		GetCmdArg(5, arg5, sizeof(arg5));
   781 		if (!StringToIntEx(arg5, immunity))
   782 		{
   783 			ReplyToCommand(client, "[SM] %t", "Invalid immunity");
   784 			return Plugin_Handled;
   785 		}
   786 	}
   787 	
   788 	decl String:identity[65];
   789 	decl String:safe_identity[140];
   790 	GetCmdArg(3, identity, sizeof(identity));
   791 	
   792 	decl String:query[256];
   793 	new Handle:hQuery;
   794 	new Handle:db = Connect();
   795 	if (db == INVALID_HANDLE)
   796 	{
   797 		ReplyToCommand(client, "[SM] %t", "Could not connect to database");
   798 		return Plugin_Handled;
   799 	}
   800 	
   801 	SQL_EscapeString(db, identity, safe_identity, sizeof(safe_identity));
   802 	
   803 	Format(query, sizeof(query), "SELECT id FROM sm_admins WHERE authtype = '%s' AND identity = '%s'", authtype, identity);
   804 	if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE)
   805 	{
   806 		return DoError(client, db, query, "Admin retrieval query failed");
   807 	}
   808 	
   809 	if (SQL_GetRowCount(hQuery) > 0)
   810 	{
   811 		ReplyToCommand(client, "[SM] %t", "SQL Admin already exists");
   812 		CloseHandle(hQuery);
   813 		CloseHandle(db);
   814 		return Plugin_Handled;
   815 	}
   816 	
   817 	CloseHandle(hQuery);
   818 	
   819 	decl String:alias[64];
   820 	decl String:safe_alias[140];
   821 	GetCmdArg(1, alias, sizeof(alias));
   822 	SQL_EscapeString(db, alias, safe_alias, sizeof(safe_alias));
   823 	
   824 	decl String:flags[30];
   825 	decl String:safe_flags[64];
   826 	GetCmdArg(4, flags, sizeof(flags));
   827 	SQL_EscapeString(db, flags, safe_flags, sizeof(safe_flags));
   828 	
   829 	decl String:password[32];
   830 	decl String:safe_password[80];
   831 	if (args > 4)
   832 	{
   833 		GetCmdArg(5, password, sizeof(password));
   834 		SQL_EscapeString(db, password, safe_password, sizeof(safe_password));
   835 	} else {
   836 		safe_password[0] = '\0';
   837 	}
   838 	
   839 	new len = 0;
   840 	len += Format(query[len], sizeof(query)-len, "INSERT INTO sm_admins (authtype, identity, password, flags, name, immunity) VALUES");
   841 	if (safe_password[0] == '\0')
   842 	{
   843 		len += Format(query[len], sizeof(query)-len, " ('%s', '%s', NULL, '%s', '%s', %d)", authtype, safe_identity, safe_flags, safe_alias, immunity);
   844 	} else {
   845 		len += Format(query[len], sizeof(query)-len, " ('%s', '%s', '%s', '%s', '%s', %d)", authtype, safe_identity, safe_password, safe_flags, safe_alias, immunity);
   846 	}
   847 	
   848 	if (!SQL_FastQuery(db, query))
   849 	{
   850 		return DoError(client, db, query, "Admin insertion query failed");
   851 	}
   852 	
   853 	ReplyToCommand(client, "[SM] %t", "SQL Admin added");
   854 	
   855 	CloseHandle(db);
   856 		
   857 	return Plugin_Handled;
   858 }
   859 
   860 stock bool:DoQuery(client, Handle:db, const String:query[])
   861 {
   862 	if (!SQL_FastQuery(db, query))
   863 	{
   864 		decl String:error[255];
   865 		SQL_GetError(db, error, sizeof(error));
   866 		LogError("Query failed: %s", error);
   867 		LogError("Query dump: %s", query);
   868 		ReplyToCommand(client, "[SM] %t", "Failed to query database");
   869 		return false;
   870 	}
   871 
   872 	return true;
   873 }
   874 
   875 stock Action:DoError(client, Handle:db, const String:query[], const String:msg[])
   876 {
   877 		decl String:error[255];
   878 		SQL_GetError(db, error, sizeof(error));
   879 		LogError("%s: %s", msg, error);
   880 		LogError("Query dump: %s", query);
   881 		CloseHandle(db);
   882 		ReplyToCommand(client, "[SM] %t", "Failed to query database");
   883 		return Plugin_Handled;
   884 }
   885 
   886 stock Action:DoStmtError(client, Handle:db, const String:query[], const String:error[], const String:msg[])
   887 {
   888 		LogError("%s: %s", msg, error);
   889 		LogError("Query dump: %s", query);
   890 		CloseHandle(db);
   891 		ReplyToCommand(client, "[SM] %t", "Failed to query database");
   892 		return Plugin_Handled;
   893 }
   894