/** * This file is part of Hercules. * http://herc.ws - http://github.com/HerculesWS/Hercules * * Copyright (C) 2012-2018 Hercules Dev Team * Copyright (C) Athena Dev Teams * * Hercules is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define HERCULES_CORE #include "pc_groups.h" #include "map/atcommand.h" // atcommand-"exists(), atcommand-"load_groups() #include "map/clif.h" // clif-"GM_kick() #include "map/map.h" // mapiterator #include "map/pc.h" // pc-"set_group() #include "common/cbasetypes.h" #include "common/conf.h" #include "common/db.h" #include "common/memmgr.h" #include "common/nullpo.h" #include "common/showmsg.h" #include "common/strlib.h" // strcmp static GroupSettings dummy_group; ///< dummy group used in dummy map sessions @see pc_get_dummy_sd() static struct pc_groups_interface pcg_s; struct pc_groups_interface *pcg; /** * Returns dummy group. * Used in dummy map sessions. * @see pc_get_dummy_sd() */ static GroupSettings *pc_group_get_dummy_group(void) { return &dummy_group; } /** * @retval NULL if not found * @private */ static inline GroupSettings *name2group(const char *group_name) { return strdb_get(pcg->name_db, group_name); } /** * Loads group configuration from config file into memory. * @private */ static void read_config(void) { struct config_t pc_group_config; struct config_setting_t *groups = NULL; const char *config_filename = "conf/groups.conf"; // FIXME hardcoded name int group_count = 0; if (!libconfig->load_file(&pc_group_config, config_filename)) return; groups = libconfig->lookup(&pc_group_config, "groups"); if (groups != NULL) { GroupSettings *group_settings = NULL; struct DBIterator *iter = NULL; int i, loop = 0; group_count = libconfig->setting_length(groups); for (i = 0; i < group_count; ++i) { int id = 0, level = 0; const char *groupname = NULL; int log_commands = 0; struct config_setting_t *group = libconfig->setting_get_elem(groups, i); if (!libconfig->setting_lookup_int(group, "id", &id)) { ShowConfigWarning(group, "pc_groups:read_config: \"groups\" list member #%d has undefined id, removing...", i); libconfig->setting_remove_elem(groups, i); --i; --group_count; continue; } if (pcg->exists(id)) { ShowConfigWarning(group, "pc_groups:read_config: duplicate group id %d, removing...", i); libconfig->setting_remove_elem(groups, i); --i; --group_count; continue; } libconfig->setting_lookup_int(group, "level", &level); libconfig->setting_lookup_bool(group, "log_commands", &log_commands); if (!libconfig->setting_lookup_string(group, "name", &groupname)) { char temp[20]; struct config_setting_t *name = NULL; snprintf(temp, sizeof(temp), "Group %d", id); if ((name = config_setting_add(group, "name", CONFIG_TYPE_STRING)) == NULL || !config_setting_set_string(name, temp)) { ShowError("pc_groups:read_config: failed to set missing group name, id=%d, skipping... (%s:%u)\n", id, config_setting_source_file(group), config_setting_source_line(group)); --i; --group_count; continue; } libconfig->setting_lookup_string(group, "name", &groupname); // Retrieve the pointer } if (name2group(groupname) != NULL) { ShowConfigWarning(group, "pc_groups:read_config: duplicate group name %s, removing...", groupname); libconfig->setting_remove_elem(groups, i); --i; --group_count; continue; } CREATE(group_settings, GroupSettings, 1); group_settings->id = id; group_settings->level = level; group_settings->name = aStrdup(groupname); group_settings->log_commands = (bool)log_commands; group_settings->inherit = libconfig->setting_get_member(group, "inherit"); group_settings->commands = libconfig->setting_get_member(group, "commands"); group_settings->permissions = libconfig->setting_get_member(group, "permissions"); group_settings->inheritance_done = false; group_settings->root = group; group_settings->index = i; strdb_put(pcg->name_db, groupname, group_settings); idb_put(pcg->db, id, group_settings); } group_count = libconfig->setting_length(groups); // Save number of groups assert(group_count == db_size(pcg->db)); // Check if all commands and permissions exist iter = db_iterator(pcg->db); for (group_settings = dbi_first(iter); dbi_exists(iter); group_settings = dbi_next(iter)) { struct config_setting_t *commands = group_settings->commands, *permissions = group_settings->permissions; int count = 0; // Make sure there is "commands" group if (commands == NULL) commands = group_settings->commands = libconfig->setting_add(group_settings->root, "commands", CONFIG_TYPE_GROUP); count = libconfig->setting_length(commands); for (i = 0; i < count; ++i) { struct config_setting_t *command = libconfig->setting_get_elem(commands, i); const char *name = config_setting_name(command); if (!atcommand->exists(name)) { ShowConfigWarning(command, "pc_groups:read_config: non-existent command name '%s', removing...", name); libconfig->setting_remove(commands, name); --i; --count; } } // Make sure there is "permissions" group if (permissions == NULL) permissions = group_settings->permissions = libconfig->setting_add(group_settings->root, "permissions", CONFIG_TYPE_GROUP); count = libconfig->setting_length(permissions); for(i = 0; i < count; ++i) { struct config_setting_t *permission = libconfig->setting_get_elem(permissions, i); const char *name = config_setting_name(permission); int j; ARR_FIND(0, pcg->permission_count, j, strcmp(pcg->permissions[j].name, name) == 0); if (j == pcg->permission_count) { ShowConfigWarning(permission, "pc_groups:read_config: non-existent permission name '%s', removing...", name); libconfig->setting_remove(permissions, name); --i; --count; } } } dbi_destroy(iter); // Apply inheritance i = 0; // counter for processed groups while (i < group_count) { iter = db_iterator(pcg->db); for (group_settings = dbi_first(iter); dbi_exists(iter); group_settings = dbi_next(iter)) { struct config_setting_t *inherit = NULL, *commands = group_settings->commands, *permissions = group_settings->permissions; int j, inherit_count = 0, done = 0; if (group_settings->inheritance_done) // group already processed continue; if ((inherit = group_settings->inherit) == NULL || (inherit_count = libconfig->setting_length(inherit)) <= 0) { // this group does not inherit from others ++i; group_settings->inheritance_done = true; continue; } for (j = 0; j < inherit_count; ++j) { GroupSettings *inherited_group = NULL; const char *groupname = libconfig->setting_get_string_elem(inherit, j); if (groupname == NULL) { ShowConfigWarning(inherit, "pc_groups:read_config: \"inherit\" array member #%d is not a name, removing...", j); libconfig->setting_remove_elem(inherit,j); continue; } if ((inherited_group = name2group(groupname)) == NULL) { ShowConfigWarning(inherit, "pc_groups:read_config: non-existent group name \"%s\", removing...", groupname); libconfig->setting_remove_elem(inherit,j); continue; } if (!inherited_group->inheritance_done) continue; // we need to do that group first // Copy settings (commands/permissions) that are not defined yet if (inherited_group->commands != NULL) { int k = 0, commands_count = libconfig->setting_length(inherited_group->commands); for (k = 0; k < commands_count; ++k) libconfig->setting_copy(commands, libconfig->setting_get_elem(inherited_group->commands, k)); } if (inherited_group->permissions != NULL) { int k = 0, permissions_count = libconfig->setting_length(inherited_group->permissions); for (k = 0; k < permissions_count; ++k) libconfig->setting_copy(permissions, libconfig->setting_get_elem(inherited_group->permissions, k)); } ++done; // copied commands and permissions from one of inherited groups } if (done == inherit_count) { // copied commands from all of inherited groups ++i; group_settings->inheritance_done = true; // we're done with this group } } dbi_destroy(iter); if (++loop > group_count) { ShowWarning("pc_groups:read_config: Could not process inheritance rules, check your config '%s' for cycles...\n", config_filename); break; } } // while(i < group_count) // Pack permissions into GroupSettings.e_permissions for faster checking iter = db_iterator(pcg->db); for (group_settings = dbi_first(iter); dbi_exists(iter); group_settings = dbi_next(iter)) { struct config_setting_t *permissions = group_settings->permissions; int count = libconfig->setting_length(permissions); for (i = 0; i < count; ++i) { struct config_setting_t *perm = libconfig->setting_get_elem(permissions, i); const char *name = config_setting_name(perm); int val = libconfig->setting_get_bool(perm); int j; if (val == 0) // does not have this permission continue; ARR_FIND(0, pcg->permission_count, j, strcmp(pcg->permissions[j].name, name) == 0); group_settings->e_permissions |= pcg->permissions[j].permission; } } dbi_destroy(iter); // Atcommand permissions are processed by atcommand module. // Fetch all groups and relevant config setting and send them // to atcommand->load_group() for processing. if (group_count > 0) { GroupSettings **pc_groups = NULL; struct config_setting_t **commands = NULL; CREATE(pc_groups, GroupSettings*, group_count); CREATE(commands, struct config_setting_t*, group_count); i = 0; iter = db_iterator(pcg->db); for (group_settings = dbi_first(iter); dbi_exists(iter); group_settings = dbi_next(iter)) { pc_groups[i] = group_settings; commands[i] = group_settings->commands; i++; } atcommand->load_groups(pc_groups, commands, group_count); dbi_destroy(iter); aFree(pc_groups); aFree(commands); } } ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' groups in '"CL_WHITE"%s"CL_RESET"'.\n", group_count, config_filename); // All data is loaded now, discard config libconfig->destroy(&pc_group_config); } /** * Checks if player group has a permission * @param group group * @param permission permission to check */ static bool pc_group_has_permission(GroupSettings *group, unsigned int permission) { nullpo_retr(false, group); return ((group->e_permissions&permission) != 0); } /** * Checks if commands used by player group should be logged * @param group group */ static bool pc_group_should_log_commands(GroupSettings *group) { nullpo_retr(true, group); return group->log_commands; } /** * Checks if player group with given ID exists. * @param group_id group id * @returns true if group exists, false otherwise */ static bool pc_group_exists(int group_id) { return idb_exists(pcg->db, group_id); } /** * @retval NULL if not found */ static GroupSettings *pc_group_id2group(int group_id) { return idb_get(pcg->db, group_id); } /** * Group name lookup. Used only in @who atcommands. * @param group group * @return group name * @public */ static const char *pc_group_get_name(GroupSettings *group) { nullpo_retr(NULL, group); return group->name; } /** * Group level lookup. A way to provide backward compatibility with GM level system. * @param group group * @return group level * @public */ static int pc_group_get_level(GroupSettings *group) { nullpo_ret(group); return group->level; } /** * Group -> index lookup. * @param group group * @return group index * @public */ static int pc_group_get_idx(GroupSettings *group) { nullpo_ret(group); return group->index; } /** * Insert a new permission * @return inserted key or 0 upon failure. **/ static unsigned int pc_groups_add_permission(const char *name) { uint64 key = 0x1; unsigned char i; nullpo_ret(name); for(i = 0; i < pcg->permission_count; i++) { if( strcmpi(name,pcg->permissions[i].name) == 0 ) { ShowError("pc_groups_add_permission(%s): failed! duplicate permission name!\n",name); return 0; } } if( i != 0 ) key = (uint64)pcg->permissions[i - 1].permission << 1; if( key >= UINT_MAX ) { ShowError("pc_groups_add_permission(%s): failed! not enough room, too many permissions!\n",name); return 0; } i = pcg->permission_count; RECREATE(pcg->permissions, struct pc_groups_permission_table, ++pcg->permission_count); pcg->permissions[i].name = aStrdup(name); pcg->permissions[i].permission = (unsigned int)key; return (unsigned int)key; } /** * Initialize PC Groups: allocate DBMaps and read config. * @public */ static void do_init_pc_groups(void) { const struct { const char *name; unsigned int permission; } pc_g_defaults[] = { { "can_trade", PC_PERM_TRADE }, { "can_party", PC_PERM_PARTY }, { "all_skill", PC_PERM_ALL_SKILL }, { "all_equipment", PC_PERM_USE_ALL_EQUIPMENT }, { "skill_unconditional", PC_PERM_SKILL_UNCONDITIONAL }, { "join_chat", PC_PERM_JOIN_ALL_CHAT }, { "kick_chat", PC_PERM_NO_CHAT_KICK }, { "hide_session", PC_PERM_HIDE_SESSION }, { "who_display_aid", PC_PERM_WHO_DISPLAY_AID }, { "hack_info", PC_PERM_RECEIVE_HACK_INFO }, { "any_warp", PC_PERM_WARP_ANYWHERE }, { "view_hpmeter", PC_PERM_VIEW_HPMETER }, { "view_equipment", PC_PERM_VIEW_EQUIPMENT }, { "use_check", PC_PERM_USE_CHECK }, { "use_changemaptype", PC_PERM_USE_CHANGEMAPTYPE }, { "all_commands", PC_PERM_USE_ALL_COMMANDS }, { "receive_requests", PC_PERM_RECEIVE_REQUESTS }, { "show_bossmobs", PC_PERM_SHOW_BOSS }, { "disable_pvm", PC_PERM_DISABLE_PVM }, { "disable_pvp", PC_PERM_DISABLE_PVP }, { "disable_commands_when_dead", PC_PERM_DISABLE_CMD_DEAD }, { "hchsys_admin", PC_PERM_HCHSYS_ADMIN }, { "can_trade_bound", PC_PERM_TRADE_BOUND }, { "disable_pickup", PC_PERM_DISABLE_PICK_UP }, { "disable_store", PC_PERM_DISABLE_STORE }, { "disable_exp", PC_PERM_DISABLE_EXP }, { "disable_skill_usage", PC_PERM_DISABLE_SKILL_USAGE }, { "bypass_nostorage", PC_PERM_BYPASS_NOSTORAGE }, }; unsigned char i, len = ARRAYLENGTH(pc_g_defaults); for(i = 0; i < len; i++) { unsigned int p; if( ( p = pc_groups_add_permission(pc_g_defaults[i].name) ) != pc_g_defaults[i].permission ) ShowError("do_init_pc_groups: %s error : %u != %u\n", pc_g_defaults[i].name, p, pc_g_defaults[i].permission); } /** * Handle plugin-provided permissions **/ for(i = 0; i < pcg->HPMpermissions_count; i++) { *pcg->HPMpermissions[i].mask = pc_groups_add_permission(pcg->HPMpermissions[i].name); } pcg->db = idb_alloc(DB_OPT_RELEASE_DATA); pcg->name_db = stridb_alloc(DB_OPT_DUP_KEY, 0); read_config(); } /** * @see DBApply */ static int group_db_clear_sub(union DBKey key, struct DBData *data, va_list args) { GroupSettings *group = DB->data2ptr(data); nullpo_ret(group); if (group->name) aFree(group->name); return 0; } /** * Finalize PC Groups: free DBMaps and config. * @public */ static void do_final_pc_groups(void) { if (pcg->db != NULL) pcg->db->destroy(pcg->db, group_db_clear_sub); if (pcg->name_db != NULL) db_destroy(pcg->name_db); if(pcg->permissions != NULL) { unsigned char i; for(i = 0; i < pcg->permission_count; i++) aFree(pcg->permissions[i].name); aFree(pcg->permissions); pcg->permissions = NULL; } pcg->permission_count = 0; } /** * Reload PC Groups * Used in @reloadatcommand * @public */ static void pc_groups_reload(void) { struct map_session_data *sd = NULL; struct s_mapiterator *iter; pcg->final(); pcg->init(); /* refresh online users permissions */ iter = mapit_getallusers(); for (sd = BL_UCAST(BL_PC, mapit->first(iter)); mapit->exists(iter); sd = BL_UCAST(BL_PC, mapit->next(iter))) { if (pc->set_group(sd, sd->group_id) != 0) { ShowWarning("pc_groups_reload: %s (AID:%d) has unknown group id (%d)! kicking...\n", sd->status.name, sd->status.account_id, pc_get_group_id(sd)); clif->GM_kick(NULL, sd); } } mapit->free(iter); } /** * Connect Interface **/ void pc_groups_defaults(void) { pcg = &pcg_s; /* */ pcg->db = NULL; pcg->name_db = NULL; /* */ pcg->permissions = NULL; pcg->permission_count = 0; /* */ pcg->HPMpermissions = NULL; pcg->HPMpermissions_count = 0; /* */ pcg->init = do_init_pc_groups; pcg->final = do_final_pc_groups; pcg->reload = pc_groups_reload; /* */ pcg->get_dummy_group = pc_group_get_dummy_group; pcg->exists = pc_group_exists; pcg->id2group = pc_group_id2group; pcg->has_permission = pc_group_has_permission; pcg->should_log_commands = pc_group_should_log_commands; pcg->get_name = pc_group_get_name; pcg->get_level = pc_group_get_level; pcg->get_idx = pc_group_get_idx; }