From 6aec628ab02b198e2b45b06c6b9377f88e9166c2 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 10 Sep 2007 23:38:58 +0000 Subject: [PATCH] - completely overhauled the immunity system - updated and reorganized database schema files - changed config files to show new immunity rules - updated sql-admin-manager so it can update+create tables - added compile.sh file for building plugins in batch - deprecated the old admin-cache immunity api relying on ImmunityType - added a new sm_config table to the schema for storing version numbers --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%401409 --- configs/admin_groups.cfg | 14 +- configs/admin_levels.cfg | 3 +- configs/admin_overrides.cfg | 1 + configs/admins.cfg | 5 + configs/admins_simple.ini | 16 +- configs/cfg/sourcemod.cfg | 10 + .../{admins-mysql.sql => create_admins.sql} | 12 +- .../mysql/update_admins_r1409.sql | 15 + .../sql-init-scripts/sqlite/admins-sqlite.sq3 | Bin 14336 -> 17408 bytes .../{admins-sqlite.sql => create_admins.sql} | 16 +- .../sqlite/update_admins-r1409.sql | 23 ++ core/AdminCache.cpp | 148 +++++-- core/AdminCache.h | 10 +- core/smn_admin.cpp | 25 ++ plugins/admin-flatfile/admin-groups.sp | 46 ++- plugins/admin-flatfile/admin-simple.sp | 31 +- plugins/admin-flatfile/admin-users.sp | 21 +- plugins/admin-sql-prefetch.sp | 31 +- plugins/admin-sql-threaded.sp | 98 +---- plugins/compile.sh | 11 + plugins/include/admin.inc | 67 +++- plugins/sql-admin-manager.sp | 361 +++++++++++++++++- public/IAdminSystem.h | 84 +++- 23 files changed, 815 insertions(+), 233 deletions(-) rename configs/sql-init-scripts/mysql/{admins-mysql.sql => create_admins.sql} (73%) create mode 100644 configs/sql-init-scripts/mysql/update_admins_r1409.sql rename configs/sql-init-scripts/sqlite/{admins-sqlite.sql => create_admins.sql} (73%) create mode 100644 configs/sql-init-scripts/sqlite/update_admins-r1409.sql create mode 100755 plugins/compile.sh diff --git a/configs/admin_groups.cfg b/configs/admin_groups.cfg index 2494210b..d464d27c 100644 --- a/configs/admin_groups.cfg +++ b/configs/admin_groups.cfg @@ -4,12 +4,15 @@ Groups * Allowed properties for a group: * * "flags" - Flag string. - * "immunity" - Specifies a group to be immune to. Use "*" for all or "$" for users with no group. - * This key may be used multiple times. + * "immunity" - Immunity level number, or a group name. + * If the group name is a number, prepend it with an + * '@' symbol similar to admins_simple.ini. Users + * will only inherit the level number if it's higher + * than their current value. */ "Default" { - "immunity" "$" + "immunity" "1" } "Full Admins" @@ -25,6 +28,9 @@ Groups { } "flags" "abcdefghiz" - "immunity" "*" + + /* Largish number for lots of in-between values. */ + "immunity" "99" } } + diff --git a/configs/admin_levels.cfg b/configs/admin_levels.cfg index 35d884b6..3c2c5769 100644 --- a/configs/admin_levels.cfg +++ b/configs/admin_levels.cfg @@ -41,7 +41,8 @@ Levels /** * Root is a magic access flag that grants all permissions. * This should only be given to trusted administrators. - * Root users can only be targetted by other root users. + * Root users can target anyone regardless of immunity, + * however, they themselves are not automatically immune. */ "root" "z" } diff --git a/configs/admin_overrides.cfg b/configs/admin_overrides.cfg index 7711099b..b0db2b94 100644 --- a/configs/admin_overrides.cfg +++ b/configs/admin_overrides.cfg @@ -14,3 +14,4 @@ Overrides * any setting that csdm_enable previously had. */ } + diff --git a/configs/admins.cfg b/configs/admins.cfg index 856df670..ed1e481f 100644 --- a/configs/admins.cfg +++ b/configs/admins.cfg @@ -19,6 +19,10 @@ * "password" - Optional password to require. * "group" - Adds one group to the user's group table. * "flags" - Adds one or more flags to the user's permissions. + * "immunity" - Sets the user's immunity level (0 = no immunity). + * Immunity can be any value. Admins with higher + * values cannot be targetted. See sm_immunity_mode + * to tweak the rules. Default value is 0. * * Example: "BAILOPAN" @@ -32,3 +36,4 @@ Admins { } + diff --git a/configs/admins_simple.ini b/configs/admins_simple.ini index 989665a7..40a9b70d 100644 --- a/configs/admins_simple.ini +++ b/configs/admins_simple.ini @@ -11,11 +11,18 @@ // Flag definitions are in "admin_levels.cfg" // You can combine flags into a string like this: // "abcdefgh" -// There is also one additional "magic" flag, $, which gives admins default immunity. // // If you want to specify a group instead of a flag, use an @ symbol. Example: // "@Full Admins" // +// You can also specify immunity values. Two examples: +// "83:abcdefg" //Immunity is 83, flags are abcefgh +// "6:@Full Admins" //Immunity is 6, group is "Full Admins" +// +// Immunity values can be any number. An admin cannot target an admin with +// a higher access value (see sm_immunity_mode to tweak the rules). Default +// immunity value is 0 (no immunity). +// // PASSWORDS: // Passwords are generally not needed unless you have name-based authentication. // In this case, admins must type this in their console: @@ -28,9 +35,10 @@ // the password being set. // //////////////////////////////// -// Examples: -// "STEAM_0:1:16" "bce" //kick, ban, slay for this steam ID -// "127.0.0.1" "z" //all permissions for this ip +// Examples: (do not put // in front of real lines, as // means 'comment') +// +// "STEAM_0:1:16" "bce" //kick, ban, slay for this steam ID, no immunity +// "127.0.0.1" "99:z" //all permissions for this ip, immunity value is 99 // "BAILOPAN" "abc" "Gab3n" //name BAILOPAN, password "Gab3n": gets reservation, kick, ban // //////////////////////////////// diff --git a/configs/cfg/sourcemod.cfg b/configs/cfg/sourcemod.cfg index a24edc86..ed89db52 100644 --- a/configs/cfg/sourcemod.cfg +++ b/configs/cfg/sourcemod.cfg @@ -33,6 +33,16 @@ sm_vote_delay 30 // 12 hour format: %m/%d/%Y - %I:%M:%S %p sm_datetime_format "%m/%d/%Y - %H:%M:%S" +// Sets how SourceMod should check immunity levels when administrators target +// each other. +// 0: Ignore immunity levels (except for specific group immunities). +// 1: Protect from admins of lower access only. +// 2: Protect from admins of equal to or lower access. +// 3: Same as 2, except admins with no immunity can affect each other. +// -- +// Default: 1 +sm_immunity_mode 1 + // Sets how many seconds SourceMod should adjust time values for incorrect // server clocks. This can be positive or negative and will affect every // system time in SourceMod, including logging stamps. diff --git a/configs/sql-init-scripts/mysql/admins-mysql.sql b/configs/sql-init-scripts/mysql/create_admins.sql similarity index 73% rename from configs/sql-init-scripts/mysql/admins-mysql.sql rename to configs/sql-init-scripts/mysql/create_admins.sql index 13ff9b99..0791aa34 100644 --- a/configs/sql-init-scripts/mysql/admins-mysql.sql +++ b/configs/sql-init-scripts/mysql/create_admins.sql @@ -6,14 +6,15 @@ CREATE TABLE sm_admins ( password varchar(65), flags varchar(30) NOT NULL, name varchar(65) NOT NULL, + immunity int(10) unsigned NOT NULL, PRIMARY KEY (id) ); CREATE TABLE sm_groups ( id int(10) unsigned NOT NULL auto_increment, - immunity enum('none','global','default') NOT NULL, flags varchar(30) NOT NULL, name varchar(120) NOT NULL, + immunity_level int(1) unsigned NOT NULL, PRIMARY KEY (id) ); @@ -44,3 +45,12 @@ CREATE TABLE sm_admins_groups ( inherit_order int(10) NOT NULL, PRIMARY KEY (admin_id, group_id) ); + +CREATE TABLE IF NOT EXISTS sm_config ( + cfg_key varchar(32) NOT NULL, + cfg_value varchar(255) NOT NULL, + PRIMARY KEY (cfg_key) +); + +INSERT INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '1.0.0.1409') ON DUPLICATE KEY UPDATE cfg_value = '1.0.0.1409'; + diff --git a/configs/sql-init-scripts/mysql/update_admins_r1409.sql b/configs/sql-init-scripts/mysql/update_admins_r1409.sql new file mode 100644 index 00000000..81c0992c --- /dev/null +++ b/configs/sql-init-scripts/mysql/update_admins_r1409.sql @@ -0,0 +1,15 @@ + +ALTER TABLE sm_admins ADD immunity INT UNSIGNED NOT NULL; + +ALTER TABLE sm_groups ADD immunity_level INT UNSIGNED NOT NULL; +UPDATE sm_groups SET immunity_level = 2 WHERE immunity = 'default'; +UPDATE sm_groups SET immunity_level = 1 WHERE immunity = 'global'; +ALTER TABLE sm_groups DROP immunity; + +CREATE TABLE sm_config ( + cfg_key varchar(32) NOT NULL, + cfg_value varchar(255) NOT NULL, + PRIMARY KEY (cfg_key) +); +INSERT INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '1.0.0.1409') ON DUPLICATE KEY UPDATE cfg_value = '1.0.0.1409'; + diff --git a/configs/sql-init-scripts/sqlite/admins-sqlite.sq3 b/configs/sql-init-scripts/sqlite/admins-sqlite.sq3 index 2b01c1c7aee47c8f5c8ed369003e21fa1e9cf9f2..c68bc42e57a9b467a857e4e3490dbc94c05a53b7 100644 GIT binary patch delta 442 zcmZoDXy{;^AT7wjfC|_r>e%owF@FPz3Nrs@{qZmuGD8-26sCoN@97MHYd~+}zT<%#un4PrndXch{iFzZBy*?U}*q zntUfGDn(BgRko1Q;Zjh5DUHubElbUTYj9CMz{A3r$iSG$xP<9Gv&zQCK*q^?)txst zsaZ2lwpDYToTz$dlK_hV8zb`*2IeO~7v5r4lVW66S4>RF&CH80OD!tS%+E8_GXO$E z69Y>wpl2AF-!U-11FC((tRl#)&WNIvkr$|fmGKP&;~OSbrmxIq%x5yeRRJ!npaJw}a$0(P zc53D1x2lq2Aa+?|PHCz_Sz=LgMq-hMk*TTXez5GGXDmNaxwqjEXdNsy!jt9hYZ&v28KsWtW4p|{u3K7 zaWyG0vWv^hGq&(=W>pktoNTES%2~<`7HYPfJY6Z8BQrO*G%vHHa&n!Tw4nx!r{L+Q zprM|ZpO>nxqoAIWnwD6aQvzb9=j10P=73mmagic = USR_MAGIC_SET; pUser->auth.identidx = -1; pUser->auth.index = 0; - pUser->immune_default = false; - pUser->immune_global = false; + pUser->immunity_level = 0; pUser->serialchange = 1; if (m_FirstUser == INVALID_ADMIN_ID) @@ -487,8 +489,7 @@ GroupId AdminCache::AddGroup(const char *group_name) id = m_pMemory->CreateMem(sizeof(AdminGroup), (void **)&pGroup); } - pGroup->immune_default = false; - pGroup->immune_global = false; + pGroup->immunity_level = 0; pGroup->immune_table = -1; pGroup->magic = GRP_MAGIC_SET; pGroup->next_grp = INVALID_GROUP_ID; @@ -594,7 +595,7 @@ const char *AdminCache::GetGroupName(GroupId gid) AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(gid); if (!pGroup || pGroup->magic != GRP_MAGIC_SET) { - return 0; + return NULL; } return m_pStrings->GetString(pGroup->nameidx); @@ -608,12 +609,23 @@ void AdminCache::SetGroupGenericImmunity(GroupId id, ImmunityType type, bool ena return; } - if (type == Immunity_Default) + unsigned int level = 0; + + if (enabled) { - pGroup->immune_default = enabled; - } else if (type == Immunity_Global) { - pGroup->immune_global = enabled; - } + if (type == Immunity_Default) + { + level = 1; + } else if (type == Immunity_Global) { + level = 2; + } + if (level > pGroup->immunity_level) + { + pGroup->immunity_level = level; + } + } else { + pGroup->immunity_level = 0; + } } bool AdminCache::GetGroupGenericImmunity(GroupId id, ImmunityType type) @@ -624,11 +636,11 @@ bool AdminCache::GetGroupGenericImmunity(GroupId id, ImmunityType type) return false; } - if (type == Immunity_Default) + if (type == Immunity_Default && pGroup->immunity_level >= 1) { - return pGroup->immune_default; - } else if (type == Immunity_Global) { - return pGroup->immune_global; + return true; + } else if (type == Immunity_Global && pGroup->immunity_level >= 2) { + return true; } return false; @@ -1333,13 +1345,9 @@ bool AdminCache::AdminInheritGroup(AdminId id, GroupId gid) /* Compute new effective permissions */ pUser->eflags |= pGroup->addflags; - if (pGroup->immune_default) + if (pGroup->immunity_level > pUser->immunity_level) { - pUser->immune_default = true; - } - if (pGroup->immune_global) - { - pUser->immune_global = true; + pUser->immunity_level = pGroup->immunity_level; } pUser->serialchange++; @@ -1486,6 +1494,11 @@ bool AdminCache::CanAdminTarget(AdminId id, AdminId target) return true; } + if (id == target) + { + return true; + } + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); if (!pUser || pUser->magic != USR_MAGIC_SET) { @@ -1506,23 +1519,39 @@ bool AdminCache::CanAdminTarget(AdminId id, AdminId target) return true; } - /** Fourth, if the targeted admin has global immunity, targeting fails. */ - if (pTarget->immune_global) + /** Fourth, if the targeted admin is immune from targeting admin. */ + int mode = sm_immunity_mode.GetInt(); + switch (mode) { - return false; + case 1: + { + if (pTarget->immunity_level > pUser->immunity_level) + { + return false; + } + break; + } + case 3: + { + /* If neither has any immunity, let this pass. */ + if (!pUser->immunity_level && !pTarget->immunity_level) + { + return true; + } + /* Don't break, go to the next case. */ + } + case 2: + { + if (pTarget->immunity_level >= pUser->immunity_level) + { + return false; + } + break; + } } /** - * Fifth, if the targeted admin has default immunity - * and the admin belongs to no groups, targeting fails. - */ - if (pTarget->immune_default && pUser->grp_count < 1) - { - return false; - } - - /** - * Sixth, if the targeted admin has specific immunity from the + * Fifth, if the targeted admin has specific immunity from the * targeting admin via group immunities, targeting fails. */ //:TODO: speed this up... maybe with trie hacks. @@ -1625,3 +1654,56 @@ bool AdminCache::CanAdminUseCommand(int client, const char *cmd) return g_ConCmds.CheckCommandAccess(client, cmd, bits); } + +unsigned int AdminCache::SetGroupImmunityLevel(GroupId gid, unsigned int level) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(gid); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return 0; + } + + unsigned int old_level = pGroup->immunity_level; + + pGroup->immunity_level = level; + + return old_level; +} + +unsigned int AdminCache::GetGroupImmunityLevel(GroupId gid) +{ + AdminGroup *pGroup = (AdminGroup *)m_pMemory->GetAddress(gid); + if (!pGroup || pGroup->magic != GRP_MAGIC_SET) + { + return 0; + } + + return pGroup->immunity_level; +} + +unsigned int AdminCache::SetAdminImmunityLevel(AdminId id, unsigned int level) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return 0; + } + + unsigned int old_level = pUser->immunity_level; + + pUser->immunity_level = level; + + return old_level; +} + +unsigned int AdminCache::GetAdminImmunityLevel(AdminId id) +{ + AdminUser *pUser = (AdminUser *)m_pMemory->GetAddress(id); + if (!pUser || pUser->magic != USR_MAGIC_SET) + { + return 0; + } + + return pUser->immunity_level; +} + diff --git a/core/AdminCache.h b/core/AdminCache.h index 309e1610..f7165a2d 100644 --- a/core/AdminCache.h +++ b/core/AdminCache.h @@ -50,8 +50,7 @@ using namespace SourceHook; struct AdminGroup { uint32_t magic; /* Magic flag, for memory validation (ugh) */ - bool immune_global; /* Global immunity? */ - bool immune_default; /* Default immunity? */ + unsigned int immunity_level; /* Immunity level */ /* Immune from target table (-1 = nonexistent) * [0] = number of entries * [1...N] = immune targets @@ -90,8 +89,7 @@ struct AdminUser int next_user; /* Next user in the list */ int prev_user; /* Previous user in the list */ UserAuth auth; /* Auth method for this user */ - bool immune_global; /* Whether globally immune */ - bool immune_default; /* Whether defaultly immune */ + unsigned int immunity_level; /* Immunity level */ unsigned int serialchange; /* Serial # for changes */ }; @@ -158,6 +156,10 @@ public: //IAdminSystem unsigned int GetAdminSerialChange(AdminId id); bool CanAdminUseCommand(int client, const char *cmd); const char *GetGroupName(GroupId gid); + virtual unsigned int SetGroupImmunityLevel(GroupId gid, unsigned int level); + virtual unsigned int GetGroupImmunityLevel(GroupId gid); + virtual unsigned int SetAdminImmunityLevel(AdminId id, unsigned int level); + virtual unsigned int GetAdminImmunityLevel(AdminId id); public: bool IsValidAdmin(AdminId id); private: diff --git a/core/smn_admin.cpp b/core/smn_admin.cpp index 2e232aa2..836a6cd5 100644 --- a/core/smn_admin.cpp +++ b/core/smn_admin.cpp @@ -450,6 +450,26 @@ static cell_t CreateAuthMethod(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t SetAdmGroupImmunityLevel(IPluginContext *pContext, const cell_t *params) +{ + return g_Admins.SetGroupImmunityLevel(params[1], params[2]); +} + +static cell_t GetAdmGroupImmunityLevel(IPluginContext *pContext, const cell_t *params) +{ + return g_Admins.GetGroupImmunityLevel(params[1]); +} + +static cell_t SetAdminImmunityLevel(IPluginContext *pContext, const cell_t *params) +{ + return g_Admins.SetAdminImmunityLevel(params[1], params[2]); +} + +static cell_t GetAdminImmunityLevel(IPluginContext *pContext, const cell_t *params) +{ + return g_Admins.GetAdminImmunityLevel(params[1]); +} + static cell_t FindFlagByName(IPluginContext *pContext, const cell_t *params) { char *flag; @@ -542,6 +562,11 @@ REGISTER_NATIVES(adminNatives) {"FindFlagByName", FindFlagByName}, {"FindFlagByChar", FindFlagByChar}, {"ReadFlagString", ReadFlagString}, + {"GetAdmGroupImmunityLevel",GetAdmGroupImmunityLevel}, + {"SetAdmGroupImmunityLevel",SetAdmGroupImmunityLevel}, + {"GetAdminImmunityLevel", GetAdminImmunityLevel}, + {"SetAdminImmunityLevel", SetAdminImmunityLevel}, /* -------------------------------------------------- */ {NULL, NULL}, }; + diff --git a/plugins/admin-flatfile/admin-groups.sp b/plugins/admin-flatfile/admin-groups.sp index 6c2a2a1a..968f24a7 100644 --- a/plugins/admin-flatfile/admin-groups.sp +++ b/plugins/admin-flatfile/admin-groups.sp @@ -110,22 +110,7 @@ public SMCResult:ReadGroups_KeyValue(Handle:smc, SetAdmGroupAddFlag(g_CurGrp, flag, true); } } else if (StrEqual(key, "immunity")) { - /* If it's a value we know about, use it */ - if (StrEqual(value, "*")) - { - SetAdmGroupImmunity(g_CurGrp, Immunity_Global, true); - } else if (StrEqual(value, "$")) { - SetAdmGroupImmunity(g_CurGrp, Immunity_Default, true); - } else { - /* If we can't find the group, we'll need to schedule a reparse */ - new GroupId:id = FindAdmGroup(value); - if (id != INVALID_GROUP_ID) - { - SetAdmGroupImmuneFrom(g_CurGrp, id); - } else { - g_NeedReparse = true; - } - } + g_NeedReparse = true; } } else if (g_GroupState == GROUP_STATE_OVERRIDES) { new OverrideRule:rule = Command_Deny; @@ -147,18 +132,31 @@ public SMCResult:ReadGroups_KeyValue(Handle:smc, /* Check for immunity again, core should handle double inserts */ if (StrEqual(key, "immunity")) { - if (StrEqual(value, "$")) + /* If it's a value we know about, use it */ + if (StrEqual(value, "*")) { - SetAdmGroupImmunity(g_CurGrp, Immunity_Default, true); - } else if (StrEqual(value, "*")) { - SetAdmGroupImmunity(g_CurGrp, Immunity_Global, true); + SetAdmGroupImmunityLevel(g_CurGrp, 2); + } else if (StrEqual(value, "$")) { + SetAdmGroupImmunityLevel(g_CurGrp, 1); } else { - new GroupId:id = FindAdmGroup(value); - if (id != INVALID_GROUP_ID) + new level; + if (StringToIntEx(value, level)) { - SetAdmGroupImmuneFrom(g_CurGrp, id); + SetAdmGroupImmunityLevel(g_CurGrp, level); } else { - ParseError("Unable to find group: \"%s\"", value); + new GroupId:id; + if (value[0] == '@') + { + id = FindAdmGroup(value[1]); + } else { + id = FindAdmGroup(value); + } + if (id != INVALID_GROUP_ID) + { + SetAdmGroupImmuneFrom(g_CurGrp, id); + } else { + ParseError("Unable to find group: \"%s\"", value); + } } } } diff --git a/plugins/admin-flatfile/admin-simple.sp b/plugins/admin-flatfile/admin-simple.sp index c83e0999..a9d08332 100644 --- a/plugins/admin-flatfile/admin-simple.sp +++ b/plugins/admin-flatfile/admin-simple.sp @@ -111,30 +111,42 @@ ReadAdminLine(const String:line[]) new String:flags[64]; cur_idx = BreakString(line[idx], flags, sizeof(flags)); idx += cur_idx; - - if (flags[0] == '@') + + /* Read immunity level, if any */ + new level, flag_idx; + + if ((flag_idx = StringToIntEx(flags, level)) > 0) { - new GroupId:gid = FindAdmGroup(flags[1]); + SetAdminImmunityLevel(admin, level); + if (flags[flag_idx] == ':') + { + flag_idx++; + } + } + + if (flags[flag_idx] == '@') + { + new GroupId:gid = FindAdmGroup(flags[flag_idx + 1]); if (gid == INVALID_GROUP_ID) { - ParseError("Invalid group detected: %s", flags[1]); + ParseError("Invalid group detected: %s", flags[flag_idx + 1]); return; } AdminInheritGroup(admin, gid); } else { - new len = strlen(flags); + new len = strlen(flags[flag_idx]); new bool:is_default = false; for (new i=0; i +#define CURRENT_SCHEMA_VERSION 1409 +#define SCHEMA_UPGRADE_1 1409 + +new current_version[4] = {1, 0, 0, CURRENT_SCHEMA_VERSION}; + public Plugin:myinfo = { name = "SQL Admin Manager", @@ -55,6 +60,8 @@ public OnPluginStart() RegAdminCmd("sm_sql_addgroup", Command_AddGroup, ADMFLAG_ROOT, "Adds a group to the SQL database"); RegAdminCmd("sm_sql_delgroup", Command_DelGroup, ADMFLAG_ROOT, "Removes a group from the SQL database"); RegAdminCmd("sm_sql_setadmingroups", Command_SetAdminGroups, ADMFLAG_ROOT, "Sets an admin's groups in the SQL database"); + RegServerCmd("sm_create_adm_tables", Command_CreateTables); + RegServerCmd("sm_update_adm_tables", Command_UpdateTables); } Handle:Connect() @@ -77,6 +84,310 @@ Handle:Connect() return db; } +CreateMySQL(client, Handle:db) +{ + new String:queries[7][] = + { + "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))", + "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))", + "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))", + "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))", + "CREATE TABLE sm_overrides (type enum('command','group') NOT NULL, name varchar(32) NOT NULL, flags varchar(30) NOT NULL, PRIMARY KEY (type,name))", + "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))", + "CREATE TABLE IF NOT EXISTS sm_config (cfg_key varchar(32) NOT NULL, cfg_value varchar(255) NOT NULL, PRIMARY KEY (cfg_key))" + }; + + for (new i = 0; i < 7; i++) + { + if (!DoQuery(client, db, queries[i])) + { + return; + } + } + + decl String:query[256]; + Format(query, + sizeof(query), + "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'", + CURRENT_SCHEMA_VERSION, + CURRENT_SCHEMA_VERSION); + + if (!DoQuery(client, db, query)) + { + return; + } + + ReplyToCommand(client, "[SM] Admin tables have been created."); +} + +CreateSQLite(client, Handle:db) +{ + new String:queries[7][] = + { + "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)", + "CREATE TABLE sm_groups (id INTEGER PRIMARY KEY AUTOINCREMENT, flags varchar(30) NOT NULL, name varchar(120) NOT NULL, immunity_level INTEGER NOT NULL)", + "CREATE TABLE sm_group_immunity (group_id INTEGER NOT NULL, other_id INTEGER NOT NULL, PRIMARY KEY (group_id, other_id))", + "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))", + "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))", + "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))", + "CREATE TABLE IF NOT EXISTS sm_config (cfg_key varchar(32) NOT NULL, cfg_value varchar(255) NOT NULL, PRIMARY KEY (cfg_key))" + }; + + for (new i = 0; i < 7; i++) + { + if (!DoQuery(client, db, queries[i])) + { + return; + } + } + + decl String:query[256]; + Format(query, + sizeof(query), + "REPLACE INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '1.0.0.%d')", + CURRENT_SCHEMA_VERSION); + + if (!DoQuery(client, db, query)) + { + return; + } + + ReplyToCommand(client, "[SM] Admin tables have been created."); +} + +public Action:Command_CreateTables(args) +{ + new client = 0; + new Handle:db = Connect(); + if (db == INVALID_HANDLE) + { + ReplyToCommand(client, "[SM] %t", "Could not connect to database"); + return Plugin_Handled; + } + + new String:ident[16]; + SQL_ReadDriver(db, ident, sizeof(ident)); + + if (strcmp(ident, "mysql") == 0) + { + CreateMySQL(client, db); + } else if (strcmp(ident, "sqlite") == 0) { + CreateSQLite(client, db); + } else { + ReplyToCommand(client, "[SM] Unknown driver type '%s', cannot create tables.", ident); + } + + CloseHandle(db); + + return Plugin_Handled; +} + +bool:GetUpdateVersion(client, Handle:db, versions[4]) +{ + decl String:query[256]; + new Handle:hQuery; + + Format(query, sizeof(query), "SELECT cfg_value FROM sm_config WHERE cfg_key = 'admin_version'"); + if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE) + { + DoError(client, db, query, "Version lookup query failed"); + return false; + } + if (SQL_FetchRow(hQuery)) + { + decl String:version_string[255]; + SQL_FetchString(hQuery, 0, version_string, sizeof(version_string)); + + decl String:version_numbers[4][12]; + if (ExplodeString(version_string, ".", version_numbers, 4, 12) == 4) + { + for (new i = 0; i < 4; i++) + { + versions[i] = StringToInt(version_numbers[i]); + } + } + } + + CloseHandle(hQuery); + + if (current_version[3] < versions[3]) + { + ReplyToCommand(client, "[SM] The database is newer than the expected version."); + return false; + } + + if (current_version[3] == versions[3]) + { + ReplyToCommand(client, "[SM] Your tables are already up to date."); + return false; + } + + + return true; +} + +UpdateSQLite(client, Handle:db) +{ + decl String:query[512]; + new Handle:hQuery; + + Format(query, sizeof(query), "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'sm_config'"); + if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE) + { + DoError(client, db, query, "Table lookup query failed"); + return; + } + + new bool:found = SQL_FetchRow(hQuery); + + CloseHandle(hQuery); + + new versions[4]; + if (found) + { + if (!GetUpdateVersion(client, db, versions)) + { + return; + } + } + + /* We only know about one upgrade path right now... + * 0 => 1 + */ + if (versions[3] < SCHEMA_UPGRADE_1) + { + new String:queries[8][] = + { + "ALTER TABLE sm_admins ADD immunity INTEGER DEFAULT 0 NOT NULL", + "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)", + "INSERT INTO _sm_groups_temp (id, flags, name) SELECT id, flags, name FROM sm_groups", + "UPDATE _sm_groups_temp SET immunity_level = 2 WHERE id IN (SELECT g.id FROM sm_groups g WHERE g.immunity = 'global')", + "UPDATE _sm_groups_temp SET immunity_level = 1 WHERE id IN (SELECT g.id FROM sm_groups g WHERE g.immunity = 'default')", + "DROP TABLE sm_groups", + "ALTER TABLE _sm_groups_temp RENAME TO sm_groups", + "CREATE TABLE IF NOT EXISTS sm_config (cfg_key varchar(32) NOT NULL, cfg_value varchar(255) NOT NULL, PRIMARY KEY (cfg_key))" + }; + + for (new i = 0; i < 8; i++) + { + if (!DoQuery(client, db, queries[i])) + { + return; + } + } + + Format(query, + sizeof(query), + "REPLACE INTO sm_config (cfg_key, cfg_value) VALUES ('admin_version', '1.0.0.%d')", + SCHEMA_UPGRADE_1); + + if (!DoQuery(client, db, query)) + { + return; + } + + versions[3] = SCHEMA_UPGRADE_1; + } + + ReplyToCommand(client, "[SM] Your tables are now up to date."); +} + +UpdateMySQL(client, Handle:db) +{ + decl String:query[512]; + new Handle:hQuery; + + Format(query, sizeof(query), "SHOW TABLES"); + if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE) + { + DoError(client, db, query, "Table lookup query failed"); + return; + } + + decl String:table[64]; + new bool:found = false; + while (SQL_FetchRow(hQuery)) + { + SQL_FetchString(hQuery, 0, table, sizeof(table)); + if (strcmp(table, "sm_config") == 0) + { + found = true; + } + } + CloseHandle(hQuery); + + new versions[4]; + + if (found && !GetUpdateVersion(client, db, versions)) + { + return; + } + + /* We only know about one upgrade path right now... + * 0 => 1 + */ + if (versions[3] < SCHEMA_UPGRADE_1) + { + new String:queries[6][] = + { + "CREATE TABLE IF NOT EXISTS sm_config (cfg_key varchar(32) NOT NULL, cfg_value varchar(255) NOT NULL, PRIMARY KEY (cfg_key))", + "ALTER TABLE sm_admins ADD immunity INT UNSIGNED NOT NULL", + "ALTER TABLE sm_groups ADD immunity_level INT UNSIGNED NOT NULL", + "UPDATE sm_groups SET immunity_level = 2 WHERE immunity = 'default'", + "UPDATE sm_groups SET immunity_level = 1 WHERE immunity = 'global'", + "ALTER TABLE sm_groups DROP immunity" + }; + + for (new i = 0; i < 6; i++) + { + if (!DoQuery(client, db, queries[i])) + { + return; + } + } + + decl String:upgr[48]; + Format(upgr, sizeof(upgr), "1.0.0.%d", SCHEMA_UPGRADE_1); + + 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); + if (!DoQuery(client, db, query)) + { + return; + } + + versions[3] = SCHEMA_UPGRADE_1; + } + + ReplyToCommand(client, "[SM] Your tables are now up to date."); +} + +public Action:Command_UpdateTables(args) +{ + new client = 0; + new Handle:db = Connect(); + if (db == INVALID_HANDLE) + { + ReplyToCommand(client, "[SM] %t", "Could not connect to database"); + return Plugin_Handled; + } + + new String:ident[16]; + SQL_ReadDriver(db, ident, sizeof(ident)); + + if (strcmp(ident, "mysql") == 0) + { + UpdateMySQL(client, db); + } else if (strcmp(ident, "sqlite") == 0) { + UpdateSQLite(client, db); + } else { + ReplyToCommand(client, "[SM] Unknown driver type, cannot upgrade."); + } + + CloseHandle(db); + + return Plugin_Handled; +} + public Action:Command_SetAdminGroups(client, args) { if (args < 2) @@ -296,17 +607,15 @@ public Action:Command_AddGroup(client, args) if (args < 2) { ReplyToCommand(client, "[SM] Usage: sm_sql_addgroup [immunity]"); - ReplyToCommand(client, "[SM] %t", "Invalid immunity"); return Plugin_Handled; } - new String:immunity[32] = "none"; + new immunity; if (args >= 3) { - GetCmdArg(3, immunity, sizeof(immunity)); - if (!StrEqual(immunity, "none") - && !StrEqual(immunity, "global") - && !StrEqual(immunity, "default")) + new String:arg3[32]; + GetCmdArg(3, arg3, sizeof(arg3)); + if (!StringToIntEx(arg3, immunity)) { ReplyToCommand(client, "[SM] %t", "Invalid immunity"); return Plugin_Handled; @@ -350,10 +659,9 @@ public Action:Command_AddGroup(client, args) Format(query, sizeof(query), - "INSERT INTO sm_groups (immunity, flags, name) VALUES ('%s', '%s', '%s')", - immunity, + "INSERT INTO sm_groups (flags, name, immunity_level) VALUES ('%s', '%s', '%d')", safe_flags, - safe_name); + immunity); if (!SQL_FastQuery(db, query)) { @@ -448,7 +756,7 @@ public Action:Command_AddAdmin(client, args) { if (args < 4) { - ReplyToCommand(client, "[SM] Usage: sm_sql_addadmin [password]"); + ReplyToCommand(client, "[SM] Usage: sm_sql_addadmin [immunity] [password]"); ReplyToCommand(client, "[SM] %t", "Invalid authtype"); return Plugin_Handled; } @@ -463,6 +771,18 @@ public Action:Command_AddAdmin(client, args) ReplyToCommand(client, "[SM] %t", "Invalid authtype"); return Plugin_Handled; } + + new immunity; + if (args >= 5) + { + new String:arg5[32]; + GetCmdArg(5, arg5, sizeof(arg5)); + if (!StringToIntEx(arg5, immunity)) + { + ReplyToCommand(client, "[SM] %t", "Invalid immunity"); + return Plugin_Handled; + } + } decl String:identity[65]; decl String:safe_identity[140]; @@ -516,12 +836,12 @@ public Action:Command_AddAdmin(client, args) } new len = 0; - len += Format(query[len], sizeof(query)-len, "INSERT INTO sm_admins (authtype, identity, password, flags, name) VALUES"); + len += Format(query[len], sizeof(query)-len, "INSERT INTO sm_admins (authtype, identity, password, flags, name, immunity) VALUES"); if (safe_password[0] == '\0') { - len += Format(query[len], sizeof(query)-len, " ('%s', '%s', NULL, '%s', '%s')", authtype, safe_identity, safe_flags, safe_alias); + len += Format(query[len], sizeof(query)-len, " ('%s', '%s', NULL, '%s', '%s', %d)", authtype, safe_identity, safe_flags, safe_alias, immunity); } else { - len += Format(query[len], sizeof(query)-len, " ('%s', '%s', '%s', '%s', '%s')", authtype, safe_identity, safe_password, safe_flags, safe_alias); + len += Format(query[len], sizeof(query)-len, " ('%s', '%s', '%s', '%s', '%s', %d)", authtype, safe_identity, safe_password, safe_flags, safe_alias, immunity); } if (!SQL_FastQuery(db, query)) @@ -536,6 +856,21 @@ public Action:Command_AddAdmin(client, args) return Plugin_Handled; } +stock bool:DoQuery(client, Handle:db, const String:query[]) +{ + if (!SQL_FastQuery(db, query)) + { + decl String:error[255]; + SQL_GetError(db, error, sizeof(error)); + LogError("Query failed: %s", error); + LogError("Query dump: %s", query); + ReplyToCommand(client, "[SM] %t", "Failed to query database"); + return false; + } + + return true; +} + stock Action:DoError(client, Handle:db, const String:query[], const String:msg[]) { decl String:error[255]; diff --git a/public/IAdminSystem.h b/public/IAdminSystem.h index 6beb4ee9..90522313 100644 --- a/public/IAdminSystem.h +++ b/public/IAdminSystem.h @@ -35,7 +35,7 @@ #include #define SMINTERFACE_ADMINSYS_NAME "IAdminSys" -#define SMINTERFACE_ADMINSYS_VERSION 3 +#define SMINTERFACE_ADMINSYS_VERSION 4 /** * @file IAdminSystem.h @@ -133,12 +133,12 @@ namespace SourceMod }; /** - * @brief Specifies a generic immunity type. + * @brief DEPRECATED. Specifies a generic immunity type. */ enum ImmunityType { - Immunity_Default = 1, /**< Immune from everyone with no immunity */ - Immunity_Global, /**< Immune from everyone (except root admins) */ + Immunity_Default = 1, /**< Immunity value of 1 */ + Immunity_Global, /**< Immunity value of 2 */ }; /** @@ -302,20 +302,31 @@ namespace SourceMod virtual FlagBits GetGroupAddFlags(GroupId id) =0; /** - * @brief Toggles a generic immunity type. + * @brief DEPRECATED. Sets a group's immunity level using backwards + * compatible types. + * + * If the new level being set is lower than the group's actual immunity + * level, no operation takes place. * * @param id Group id. - * @param type Generic immunity type. - * @param enabled True to enable, false otherwise. + * @param type Immunity type which will be converted to a + * numerical level. + * @param enabled True to set the level. False sets the + * group's immunity value to 0. */ virtual void SetGroupGenericImmunity(GroupId id, ImmunityType type, bool enabled) =0; /** - * @brief Returns whether or not a group has global immunity. + * @brief DEPRECATED. Returns whether a group has an immunity level + * using backwards compatible types. + * + * This simply checks whether the group's immunity value is greater + * than or equal to the new-style value for the old type. * * @param id Group id. * @param type Generic immunity type. - * @return True if the group has this immunity, false otherwise. + * @return True if the group has this immunity, false + * otherwise. */ virtual bool GetGroupGenericImmunity(GroupId id, ImmunityType type) =0; @@ -583,15 +594,17 @@ namespace SourceMod /** * @brief Checks whether an AdminId can target another AdminId. * - * Zeroth, if the targeting AdminId is INVALID_ADMIN_ID, targeting fails. - * First, if the targeted AdminId is INVALID_ADMIN_ID, targeting succeeds. - * Second, if the targeting admin is root, targeting succeeds. - * Third, if the targeted admin has global immunity, targeting fails. - * Fourth, if the targeted admin has default immunity, - * and the admin belongs to no groups, targeting fails. - * Fifth, if the targeted admin has specific immunity from the - * targeting admin via group immunities, targeting fails. - * Sixth, targeting succeeds if it passes these tests. + * The hueristics for this check are as follows: + * 0. If the targeting AdminId is INVALID_ADMIN_ID, targeting fails. + * 1. If the targeted AdminId is INVALID_ADMIN_ID, targeting succeeds. + * 2. If the targeted AdminId is the same as the targeting AdminId, + * (self) targeting succeeds. + * 3. If the targeting admin is root, targeting succeeds. + * 4. If the targeted admin has access higher (as interpreted by + * (sm_immunity_mode) than the targeting admin, then targeting fails. + * 5. If the targeted admin has specific immunity from the + * targeting admin via group immunities, targeting fails. + * 6. Targeting succeeds. * * @param id AdminId index of admin doing the targeting. Can be INVALID_ADMIN_ID. * @param target AdminId index of the target admin. Can be INVALID_ADMIN_ID. @@ -655,7 +668,42 @@ namespace SourceMod * @return Group name, or NULL on failure. */ virtual const char *GetGroupName(GroupId gid) =0; + + /** + * @brief Sets the immunity level of a group. + * + * @param gid Group Id. + * @param level Immunity level value. + * @return Old immunity level. + */ + virtual unsigned int SetGroupImmunityLevel(GroupId gid, unsigned int level) =0; + + /** + * @brief Retrieves the immunity level of a group. + * + * @param gid Group Id. + * @return Immunity level value. + */ + virtual unsigned int GetGroupImmunityLevel(GroupId gid) =0; + + /** + * @brief Sets the immunity level of an admin. + * + * @param id Admin Id. + * @param level Immunity level value. + * @return Old immunity level. + */ + virtual unsigned int SetAdminImmunityLevel(AdminId id, unsigned int level) =0; + + /** + * @brief Retrieves the immunity level of an admin. + * + * @param id Admin Id. + * @return Immunity level value. + */ + virtual unsigned int GetAdminImmunityLevel(AdminId id) =0; }; } #endif //_INCLUDE_SOURCEMOD_ADMINISTRATION_SYSTEM_H_ +