/* Base routines for ChanServ access level handling. * * IRC Services is copyright (c) 1996-2007 Andrew Church. * E-mail: * Parts written by Andrew Kempe and others. * This program is free but copyrighted software; see the file COPYING for * details. */ #include "services.h" #include "modules.h" #include "language.h" #include "modules/nickserv/nickserv.h" #include "modules/operserv/operserv.h" #include "chanserv.h" #include "access.h" #include "cs-local.h" /*************************************************************************/ static Module *module; /*************************************************************************/ /* Array of all access levels: */ LevelInfo levelinfo[] = { /* Dummy entry for channel owner; flags filled in by init_access */ { CA_AUTOOWNER, ACCLEV_FOUNDER, "", -1, CL_SET_MODE, { .cumode = {"", 1} } }, { CA_AUTOPROTECT, ACCLEV_SOP, "AUTOPROTECT", CHAN_LEVEL_AUTOPROTECT, CL_SET_MODE, { .cumode = {"a",0} } }, { CA_AUTOOP, ACCLEV_AOP, "AUTOOP", CHAN_LEVEL_AUTOOP, CL_SET_MODE, { .cumode = {"o",1} } }, { CA_AUTOHALFOP, ACCLEV_HOP, "AUTOHALFOP", CHAN_LEVEL_AUTOHALFOP, CL_SET_MODE, { .cumode = {"h",1} } }, { CA_AUTOVOICE, ACCLEV_VOP, "AUTOVOICE", CHAN_LEVEL_AUTOVOICE, CL_SET_MODE, { .cumode = {"v",0} } }, /* Internal use; not settable by users. These two levels change based * on the SECUREOPS and RESTRICTED settings. */ { CA_AUTODEOP, -1, "", -1, CL_CLEAR_MODE|CL_LESSEQUAL, { cumode: {"oha",0} } }, { CA_NOJOIN, -100, "", -1, CL_OTHER|CL_LESSEQUAL }, { CA_INVITE, ACCLEV_AOP, "INVITE", CHAN_LEVEL_INVITE, CL_ALLOW_CMD, { {"INVITE"} } }, { CA_AKICK, ACCLEV_SOP, "AKICK", CHAN_LEVEL_AKICK, CL_ALLOW_CMD, { {"AKICK"} } }, { CA_SET, ACCLEV_FOUNDER, "SET", CHAN_LEVEL_SET, CL_ALLOW_CMD, { {"SET"} } }, { CA_CLEAR, ACCLEV_SOP, "CLEAR", CHAN_LEVEL_CLEAR, CL_ALLOW_CMD, { {"CLEAR"} } }, { CA_UNBAN, ACCLEV_AOP, "UNBAN", CHAN_LEVEL_UNBAN, CL_ALLOW_CMD, { {"UNBAN"} } }, { CA_ACCESS_LIST, 0, "ACC-LIST", CHAN_LEVEL_ACCESS_LIST, CL_ALLOW_CMD, { {"ACCESS","LIST"} } }, { CA_ACCESS_CHANGE, ACCLEV_HOP, "ACC-CHANGE", CHAN_LEVEL_ACCESS_CHANGE, CL_ALLOW_CMD, { {"ACCESS"} } }, { CA_MEMO, ACCLEV_SOP, "MEMO", CHAN_LEVEL_MEMO, CL_OTHER }, { CA_OPDEOP, ACCLEV_AOP, "OP-DEOP", CHAN_LEVEL_OPDEOP, CL_ALLOW_CMD, { {"OP"} } }, /* also includes "DEOP" */ { CA_VOICE, ACCLEV_VOP, "VOICE", CHAN_LEVEL_VOICE, CL_ALLOW_CMD, { {"VOICE"} } }, { CA_HALFOP, ACCLEV_HOP, "HALFOP", CHAN_LEVEL_HALFOP, CL_ALLOW_CMD, { {"HALFOP"} } }, { CA_PROTECT, ACCLEV_SOP, "PROTECT", CHAN_LEVEL_PROTECT, CL_ALLOW_CMD, { {"PROTECT"} } }, { CA_KICK, ACCLEV_AOP, "KICK", CHAN_LEVEL_KICK, CL_ALLOW_CMD, { {"KICK"} } }, { CA_TOPIC, ACCLEV_AOP, "TOPIC", CHAN_LEVEL_TOPIC, CL_ALLOW_CMD, { {"TOPIC"} } }, { CA_STATUS, ACCLEV_SOP, "STATUS", CHAN_LEVEL_STATUS, CL_ALLOW_CMD, { {"STATUS"} } }, { -1 } }; /* Default access levels (initialized at runtime): */ int16 def_levels[CA_SIZE]; /* Which levels are "maximums" (CL_LESSEQUAL): */ static int lev_is_max[CA_SIZE]; static int get_access_if_idented(User *user, ChannelInfo *ci); /*************************************************************************/ /************************** Global-use routines **************************/ /*************************************************************************/ /* Reset channel access level values to their default state. Does not call * put_channelinfo(). If `set' is nonzero, an access level array is * allocated and initialized to default values, else any existing array is * cleared (implying the default values). */ EXPORT_FUNC(reset_levels) void reset_levels(ChannelInfo *ci, int set) { int i; free(ci->levels); if (set) { ci->levels = scalloc(CA_SIZE, sizeof(*ci->levels)); for (i = 0; i < CA_SIZE; i++) ci->levels[i] = def_levels[i]; } else { ci->levels = NULL; } } /*************************************************************************/ /* Return the access level the given user has on the channel. If the * channel doesn't exist, the user isn't on the access list, or the channel * is CS_SECURE and the user hasn't IDENTIFY'd with NickServ, return 0. */ EXPORT_FUNC(get_access) int get_access(User *user, ChannelInfo *ci) { if (is_founder(user, ci)) return ACCLEV_FOUNDER; if (!ci || !valid_ngi(user) || (ci->flags&CI_VERBOTEN) || ci->suspendinfo) return 0; if (user_identified(user) || (user_recognized(user) && !(ci->flags & CI_SECURE)) ) { int32 id = user->ngi->id; int i; ARRAY_FOREACH (i, ci->access) { if (ci->access[i].nickgroup == id) return ci->access[i].level; } } return 0; } /*************************************************************************/ /* Return 1 if the user's access level on the given channel falls into the * given category, 0 otherwise. Note that this may seem slightly confusing * in some cases: for example, check_access(..., CA_NOJOIN) returns true if * the user does _not_ have access to the channel (i.e. matches the NOJOIN * criterion). */ EXPORT_FUNC(check_access) int check_access(User *user, ChannelInfo *ci, int what) { int level = get_access(user, ci); int limit; if (level == ACCLEV_FOUNDER) return lev_is_max[what] ? 0 : 1; limit = ci->levels ? ci->levels[what] : def_levels[what]; /* Hacks to make flags work */ if (what == CA_AUTODEOP && (ci->flags & CI_SECUREOPS)) limit = 0; if (what == CA_NOJOIN && (ci->flags & CI_RESTRICTED)) limit = 0; if (limit == ACCLEV_INVALID) return 0; if (lev_is_max[what]) return level <= limit; else return level >= limit; } /*************************************************************************/ /* Do like check_access(), but return whether the user would match the * given category if they had identified for their nick. If the nick is * not registered, returns the same value as check_access(). */ EXPORT_FUNC(check_access_if_idented) int check_access_if_idented(User *user, ChannelInfo *ci, int what) { int level = get_access_if_idented(user, ci); int limit; if (level == ACCLEV_FOUNDER) return lev_is_max[what] ? 0 : 1; limit = ci->levels ? ci->levels[what] : def_levels[what]; /* Hacks to make flags work */ if (what == CA_AUTODEOP && (ci->flags & CI_SECUREOPS)) limit = 0; if (what == CA_NOJOIN && (ci->flags & CI_RESTRICTED)) limit = 0; if (limit == ACCLEV_INVALID) return 0; if (lev_is_max[what]) return level <= limit; else return level >= limit; } /*************************************************************************/ /* Return positive if the user is permitted access to the given command for * the given channel, zero otherwise. If no level is found that * corresponds to the given command, return -1. */ EXPORT_FUNC(check_access_cmd) int check_access_cmd(User *user, ChannelInfo *ci, const char *command, const char *subcommand) { int i; /* If we have a subcommand, first check for an exact match */ if (subcommand) { for (i = 0; levelinfo[i].what >= 0; i++) { if ((levelinfo[i].action & CL_TYPEMASK) == CL_ALLOW_CMD && levelinfo[i].target.cmd.sub && stricmp(command, levelinfo[i].target.cmd.main) == 0 && stricmp(subcommand, levelinfo[i].target.cmd.sub) == 0 ) { return check_access(user, ci, levelinfo[i].what); } } } /* No subcommand or no exact match, so match on command name; * explicitly skip entries with subcommands (because they didn't * match before) */ for (i = 0; levelinfo[i].what >= 0; i++) { if ((levelinfo[i].action & CL_TYPEMASK) == CL_ALLOW_CMD && !levelinfo[i].target.cmd.sub && stricmp(command, levelinfo[i].target.cmd.main) == 0 ) { return check_access(user, ci, levelinfo[i].what); } } /* No level found */ return -1; } /*************************************************************************/ /* Return the levelinfo[] array. Used by the httpd/dbaccess module. */ EXPORT_FUNC(get_levelinfo) LevelInfo *get_levelinfo(void) { return levelinfo; } /*************************************************************************/ /************************** Local-use routines ***************************/ /*************************************************************************/ /* Return the access level the given user has on the channel, supposing the * user was identified for their current nick (if it's registered). */ static int get_access_if_idented(User *user, ChannelInfo *ci) { int i; int32 id; if (is_identified(user, ci)) return ACCLEV_FOUNDER; if (!ci || !valid_ngi(user) || (ci->flags&CI_VERBOTEN) || ci->suspendinfo) return 0; if (user->ngi->id == ci->founder) return ACCLEV_FOUNDER; id = user->ngi->id; ARRAY_FOREACH (i, ci->access) { if (ci->access[i].nickgroup == id) return ci->access[i].level; } return 0; } /*************************************************************************/ /* Check a channel user mode change, and return the mask of mode flags * which should be changed (i.e. should be the reverse of the new flags * given). The `changemask' parameter indicates which flags are changing, * and `newmodes' indicates the value of the changing flags; a new user on * a channel, e.g. from JOIN, should have a `changemask' with all bits set * (because all bits are changing from "undefined" to either on or off). * The returned value will always be a subset of `changemask'. * * Example: autoprotect/autoop user joins channel * check_access_cumode(..., 0, ~0) -> CUMODE_a | CUMODE_o * Example: an autodeop user gets +o'd by somebody * check_access_cumode(..., CUMODE_o, CUMODE_o) -> CUMODE_o * (i.e. "setting CUMODE_o is incorrect" -> send a MODE -o) */ int check_access_cumode(User *user, ChannelInfo *ci, int32 newmodes, int32 changemask) { int i; int32 result = 0; for (i = 0; levelinfo[i].what >= 0; i++) { int type = levelinfo[i].action & CL_TYPEMASK; int32 flags = levelinfo[i].target.cumode.flags; int clevel = ci->levels ? ci->levels[levelinfo[i].what] : def_levels[levelinfo[i].what]; if ((type == CL_SET_MODE || type == CL_CLEAR_MODE) && clevel != ACCLEV_INVALID && (changemask & flags) && check_access(user, ci, levelinfo[i].what) ) { if (type == CL_SET_MODE && (~newmodes & flags)) result |= ~newmodes & flags; else if (type == CL_CLEAR_MODE && (newmodes & flags)) result |= newmodes & flags; while (levelinfo[i].target.cumode.cont) i++; } } return result; } /*************************************************************************/ /* Add an entry `nick' (at `level') to the access list by a user with * access `uacc'. */ int access_add(ChannelInfo *ci, const char *nick, int level, int uacc) { int i; NickInfo *ni; if (level >= uacc) return RET_PERMISSION; ni = get_nickinfo(nick); if (!ni) return RET_NOSUCHNICK; else if (ni->status & NS_VERBOTEN) return RET_NICKFORBID; else if (!check_ngi(ni)) return RET_INTERR; ARRAY_FOREACH (i, ci->access) { if (ci->access[i].nickgroup == ni->nickgroup) { /* Don't allow lowering from a level >= uacc */ if (ci->access[i].level >= uacc) return RET_PERMISSION; if (ci->access[i].level == level) return RET_UNCHANGED; ci->access[i].level = level; put_channelinfo(ci); return RET_CHANGED; } } ARRAY_SEARCH_SCALAR(ci->access, nickgroup, 0, i); if (i == ci->access_count) { if (i < CSAccessMax) { ARRAY_EXTEND(ci->access); } else { return RET_LISTFULL; } } ci->access[i].nickgroup = ni->nickgroup; ci->access[i].level = level; put_channelinfo(ci); return RET_ADDED; } /*************************************************************************/ /*************************************************************************/ int init_access(Module *mod) { int i; module = mod; /* Initialize def_levels[] and lev_is_max[], and convert mode letters * to flags */ for (i = 0; levelinfo[i].what >= 0; i++) { int type = levelinfo[i].action & CL_TYPEMASK; if (type == CL_SET_MODE || type == CL_CLEAR_MODE) { if (levelinfo[i].what == CA_AUTOOWNER) { if (chanusermode_owner) levelinfo[i].target.cumode.flags = chanusermode_owner; else levelinfo[i].action = CL_OTHER; /* make it a no-op */ } else { /* Use MODE_NOERROR to deal with protocols that don't * support some modes (e.g. +h in AUTODEOP) */ levelinfo[i].target.cumode.flags = mode_string_to_flags(levelinfo[i].target.cumode.modes, MODE_CHANUSER | MODE_NOERROR); } } def_levels[levelinfo[i].what] = levelinfo[i].defval; lev_is_max[levelinfo[i].what] = levelinfo[i].action & CL_LESSEQUAL; } /* Delete any levels for features not supported by protocol */ if (!(protocol_features & PF_HALFOP)) { int offset = 0; for (i = 0; i == 0 || levelinfo[i-1].what >= 0; i++) { if (levelinfo[i].what == CA_AUTOHALFOP || levelinfo[i].what == CA_HALFOP ) { offset++; } else if (offset) { memcpy(&levelinfo[i-offset], &levelinfo[i], sizeof(*levelinfo)); } } } if (!(protocol_features & PF_CHANPROT)) { int offset = 0; for (i = 0; i == 0 || levelinfo[i-1].what >= 0; i++) { if (levelinfo[i].what == CA_AUTOPROTECT || levelinfo[i].what == CA_PROTECT ) { offset++; } else if (offset) { memcpy(&levelinfo[i-offset], &levelinfo[i], sizeof(*levelinfo)); } } } return 1; } /*************************************************************************/ void exit_access(void) { } /*************************************************************************/