diff options
Diffstat (limited to 'src/core/cs_access.c')
-rw-r--r-- | src/core/cs_access.c | 507 |
1 files changed, 507 insertions, 0 deletions
diff --git a/src/core/cs_access.c b/src/core/cs_access.c new file mode 100644 index 000000000..e5d621972 --- /dev/null +++ b/src/core/cs_access.c @@ -0,0 +1,507 @@ +/* ChanServ core functions + * + * (C) 2003-2005 Anope Team + * Contact us at info@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + * + * $Id$ + * + */ +/*************************************************************************/ + +#include "module.h" + + +int do_access(User * u); +int do_levels(User * u); + +void myChanServHelp(User * u); + +/** + * Create the command, and tell anope about it. + * @param argc Argument count + * @param argv Argument list + * @return MOD_CONT to allow the module, MOD_STOP to stop it + **/ +int AnopeInit(int argc, char **argv) +{ + Command *c; + + moduleAddAuthor("Anope"); + moduleAddVersion("$Id$"); + moduleSetType(CORE); + + c = createCommand("ACCESS", do_access, NULL, CHAN_HELP_ACCESS, -1, -1, + -1, -1); + moduleAddCommand(CHANSERV, c, MOD_UNIQUE); + c = createCommand("ACCESS LEVELS", NULL, NULL, CHAN_HELP_ACCESS_LEVELS, + -1, -1, -1, -1); + moduleAddCommand(CHANSERV, c, MOD_UNIQUE); + c = createCommand("LEVELS", do_levels, NULL, CHAN_HELP_LEVELS, -1, -1, + -1, -1); + moduleAddCommand(CHANSERV, c, MOD_UNIQUE); + moduleSetChanHelp(myChanServHelp); + + return MOD_CONT; +} + +/** + * Unload the module + **/ +void AnopeFini(void) +{ + +} + + + +/** + * Add the help response to anopes /cs help output. + * @param u The user who is requesting help + **/ +void myChanServHelp(User * u) +{ + notice_lang(s_ChanServ, u, CHAN_HELP_CMD_ACCESS); + notice_lang(s_ChanServ, u, CHAN_HELP_CMD_LEVELS); +} + + +static int access_del(User * u, ChanAccess * access, int *perm, int uacc) +{ + if (!access->in_use) + return 0; + if (!is_services_admin(u) && uacc <= access->level) { + (*perm)++; + return 0; + } + access->nc = NULL; + access->in_use = 0; + return 1; +} + +static int access_del_callback(User * u, int num, va_list args) +{ + ChannelInfo *ci = va_arg(args, ChannelInfo *); + int *last = va_arg(args, int *); + int *perm = va_arg(args, int *); + int uacc = va_arg(args, int); + if (num < 1 || num > ci->accesscount) + return 0; + *last = num; + return access_del(u, &ci->access[num - 1], perm, uacc); +} + + +static int access_list(User * u, int index, ChannelInfo * ci, + int *sent_header) +{ + ChanAccess *access = &ci->access[index]; + char *xop; + + if (!access->in_use) + return 0; + + if (!*sent_header) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_HEADER, ci->name); + *sent_header = 1; + } + + if (ci->flags & CI_XOP) { + xop = get_xop_level(access->level); + notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_XOP_FORMAT, index + 1, + xop, access->nc->display); + } else { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_AXS_FORMAT, index + 1, + access->level, access->nc->display); + } + return 1; +} + +static int access_list_callback(User * u, int num, va_list args) +{ + ChannelInfo *ci = va_arg(args, ChannelInfo *); + int *sent_header = va_arg(args, int *); + if (num < 1 || num > ci->accesscount) + return 0; + return access_list(u, num - 1, ci, sent_header); +} + + +/** + * The /cs access command. + * @param u The user who issued the command + * @param MOD_CONT to continue processing other modules, MOD_STOP to stop processing. + **/ +int do_access(User * u) +{ + char *chan = strtok(NULL, " "); + char *cmd = strtok(NULL, " "); + char *nick = strtok(NULL, " "); + char *s = strtok(NULL, " "); + + ChannelInfo *ci; + NickAlias *na; + NickCore *nc; + ChanAccess *access; + + int i; + int level = 0, ulev; + int is_list = (cmd && stricmp(cmd, "LIST") == 0); + int is_servadmin = is_services_admin(u); + + /* If LIST, we don't *require* any parameters, but we can take any. + * If DEL, we require a nick and no level. + * Else (ADD), we require a level (which implies a nick). */ + if (!cmd || ((is_list || !stricmp(cmd, "CLEAR")) ? 0 : + (stricmp(cmd, "DEL") == 0) ? (!nick || s) : !s)) { + syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_SYNTAX); + } else if (!(ci = cs_findchan(chan))) { + notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan); + } else if (ci->flags & CI_VERBOTEN) { + notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan); + /* We still allow LIST in xOP mode, but not others */ + } else if ((ci->flags & CI_XOP) && !is_list) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_XOP, s_ChanServ); + } else if (((is_list && !check_access(u, ci, CA_ACCESS_LIST)) + || (!is_list && !check_access(u, ci, CA_ACCESS_CHANGE))) + && !is_servadmin) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else if (stricmp(cmd, "ADD") == 0) { + if (readonly) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED); + return MOD_CONT; + } + + level = atoi(s); + ulev = get_access(u, ci); + + if (!is_servadmin && level >= ulev) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + return MOD_CONT; + } + + if (level == 0) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_NONZERO); + return MOD_CONT; + } else if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_RANGE, + ACCESS_INVALID + 1, ACCESS_FOUNDER - 1); + return MOD_CONT; + } + + na = findnick(nick); + if (!na) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_NICKS_ONLY); + return MOD_CONT; + } + if (na->status & NS_VERBOTEN) { + notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, nick); + return MOD_CONT; + } + + nc = na->nc; + for (access = ci->access, i = 0; i < ci->accesscount; + access++, i++) { + if (access->nc == nc) { + /* Don't allow lowering from a level >= ulev */ + if (!is_servadmin && access->level >= ulev) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + return MOD_CONT; + } + if (access->level == level) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_UNCHANGED, + access->nc->display, chan, level); + return MOD_CONT; + } + access->level = level; + alog("%s: %s!%s@%s (level %d) set access level %d to %s (group %s) on channel %s", s_ChanServ, u->nick, u->username, u->host, ulev, access->level, na->nick, nc->display, ci->name); + notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_CHANGED, + access->nc->display, chan, level); + return MOD_CONT; + } + } + + for (i = 0; i < ci->accesscount; i++) { + if (!ci->access[i].in_use) + break; + } + if (i == ci->accesscount) { + if (i < CSAccessMax) { + ci->accesscount++; + ci->access = + srealloc(ci->access, + sizeof(ChanAccess) * ci->accesscount); + } else { + notice_lang(s_ChanServ, u, CHAN_ACCESS_REACHED_LIMIT, + CSAccessMax); + return MOD_CONT; + } + } + + access = &ci->access[i]; + access->nc = nc; + access->in_use = 1; + access->level = level; + access->last_seen = 0; + + alog("%s: %s!%s@%s (level %d) set access level %d to %s (group %s) on channel %s", s_ChanServ, u->nick, u->username, u->host, ulev, access->level, na->nick, nc->display, ci->name); + notice_lang(s_ChanServ, u, CHAN_ACCESS_ADDED, nc->display, + ci->name, access->level); + } else if (stricmp(cmd, "DEL") == 0) { + int deleted, a, b; + if (readonly) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED); + return MOD_CONT; + } + + if (ci->accesscount == 0) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, chan); + return MOD_CONT; + } + + /* Special case: is it a number/list? Only do search if it isn't. */ + if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) { + int count, last = -1, perm = 0; + deleted = process_numlist(nick, &count, access_del_callback, u, + ci, &last, &perm, get_access(u, ci)); + if (!deleted) { + if (perm) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + } else if (count == 1) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_SUCH_ENTRY, + last, ci->name); + } else { + notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_MATCH, + ci->name); + } + } else if (deleted == 1) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED_ONE, + ci->name); + } else { + notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED_SEVERAL, + deleted, ci->name); + } + } else { + na = findnick(nick); + if (!na) { + notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, nick); + return MOD_CONT; + } + nc = na->nc; + for (i = 0; i < ci->accesscount; i++) { + if (ci->access[i].nc == nc) + break; + } + if (i == ci->accesscount) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_NOT_FOUND, nick, + chan); + return MOD_CONT; + } + access = &ci->access[i]; + if (!is_servadmin && get_access(u, ci) <= access->level) { + deleted = 0; + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + } else { + notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED, + access->nc->display, ci->name); + alog("%s: %s!%s@%s (level %d) deleted access of %s (group %s) on %s", s_ChanServ, u->nick, u->username, u->host, get_access(u, ci), na->nick, access->nc->display, chan); + access->nc = NULL; + access->in_use = 0; + deleted = 1; + } + } + + if (deleted) { + /* Reordering - DrStein */ + for (b = 0; b < ci->accesscount; b++) { + if (ci->access[b].in_use) { + for (a = 0; a < ci->accesscount; a++) { + if (a > b) + break; + if (!ci->access[a].in_use) { + ci->access[a].in_use = 1; + ci->access[a].level = ci->access[b].level; + ci->access[a].nc = ci->access[b].nc; + ci->access[a].last_seen = + ci->access[b].last_seen; + ci->access[b].nc = NULL; + ci->access[b].in_use = 0; + break; + } + } + } + } + } + } else if (stricmp(cmd, "LIST") == 0) { + int sent_header = 0; + + if (ci->accesscount == 0) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, chan); + return MOD_CONT; + } + if (nick && strspn(nick, "1234567890,-") == strlen(nick)) { + process_numlist(nick, NULL, access_list_callback, u, ci, + &sent_header); + } else { + for (i = 0; i < ci->accesscount; i++) { + if (nick && ci->access[i].nc + && !match_wild_nocase(nick, ci->access[i].nc->display)) + continue; + access_list(u, i, ci, &sent_header); + } + } + if (!sent_header) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_MATCH, chan); + } else { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_FOOTER, ci->name); + } + } else if (stricmp(cmd, "CLEAR") == 0) { + + if (readonly) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED); + return MOD_CONT; + } + + if (!is_servadmin && !is_founder(u, ci)) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + return MOD_CONT; + } + + free(ci->access); + ci->access = NULL; + ci->accesscount = 0; + + notice_lang(s_ChanServ, u, CHAN_ACCESS_CLEAR, ci->name); + alog("%s: %s!%s@%s (level %d) cleared access list on %s", + s_ChanServ, u->nick, u->username, u->host, + get_access(u, ci), chan); + + } else { + syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_SYNTAX); + } + return MOD_CONT; +} + + +int do_levels(User * u) +{ + char *chan = strtok(NULL, " "); + char *cmd = strtok(NULL, " "); + char *what = strtok(NULL, " "); + char *s = strtok(NULL, " "); + char *error; + + ChannelInfo *ci; + int level; + int i; + + /* If SET, we want two extra parameters; if DIS[ABLE] or FOUNDER, we want only + * one; else, we want none. + */ + if (!cmd + || ((stricmp(cmd, "SET") == 0) ? !s + : ((strnicmp(cmd, "DIS", 3) == 0)) ? (!what || s) : !!what)) { + syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX); + } else if (!(ci = cs_findchan(chan))) { + notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan); + } else if (ci->flags & CI_VERBOTEN) { + notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan); + } else if (ci->flags & CI_XOP) { + notice_lang(s_ChanServ, u, CHAN_LEVELS_XOP); + } else if (!is_founder(u, ci) && !is_services_admin(u)) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else if (stricmp(cmd, "SET") == 0) { + level = strtol(s, &error, 10); + + if (*error != '\0') { + syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX); + return MOD_CONT; + } + + if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER) { + notice_lang(s_ChanServ, u, CHAN_LEVELS_RANGE, + ACCESS_INVALID + 1, ACCESS_FOUNDER - 1); + return MOD_CONT; + } + + for (i = 0; levelinfo[i].what >= 0; i++) { + if (stricmp(levelinfo[i].name, what) == 0) { + ci->levels[levelinfo[i].what] = level; + + alog("%s: %s!%s@%s set level %s on channel %s to %d", + s_ChanServ, u->nick, u->username, u->host, + levelinfo[i].name, ci->name, level); + notice_lang(s_ChanServ, u, CHAN_LEVELS_CHANGED, + levelinfo[i].name, chan, level); + return MOD_CONT; + } + } + + notice_lang(s_ChanServ, u, CHAN_LEVELS_UNKNOWN, what, s_ChanServ); + + } else if (stricmp(cmd, "DIS") == 0 || stricmp(cmd, "DISABLE") == 0) { + for (i = 0; levelinfo[i].what >= 0; i++) { + if (stricmp(levelinfo[i].name, what) == 0) { + ci->levels[levelinfo[i].what] = ACCESS_INVALID; + + alog("%s: %s!%s@%s disabled level %s on channel %s", + s_ChanServ, u->nick, u->username, u->host, + levelinfo[i].name, ci->name); + notice_lang(s_ChanServ, u, CHAN_LEVELS_DISABLED, + levelinfo[i].name, chan); + return MOD_CONT; + } + } + + notice_lang(s_ChanServ, u, CHAN_LEVELS_UNKNOWN, what, s_ChanServ); + } else if (stricmp(cmd, "LIST") == 0) { + int i; + + notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_HEADER, chan); + + if (!levelinfo_maxwidth) { + for (i = 0; levelinfo[i].what >= 0; i++) { + int len = strlen(levelinfo[i].name); + if (len > levelinfo_maxwidth) + levelinfo_maxwidth = len; + } + } + + for (i = 0; levelinfo[i].what >= 0; i++) { + int j = ci->levels[levelinfo[i].what]; + + if (j == ACCESS_INVALID) { + j = levelinfo[i].what; + + if (j == CA_AUTOOP || j == CA_AUTODEOP || j == CA_AUTOVOICE + || j == CA_NOJOIN) { + notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_DISABLED, + levelinfo_maxwidth, levelinfo[i].name); + } else { + notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_DISABLED, + levelinfo_maxwidth, levelinfo[i].name); + } + } else if (j == ACCESS_FOUNDER) { + notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_FOUNDER, + levelinfo_maxwidth, levelinfo[i].name); + } else { + notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_NORMAL, + levelinfo_maxwidth, levelinfo[i].name, j); + } + } + + } else if (stricmp(cmd, "RESET") == 0) { + reset_levels(ci); + + alog("%s: %s!%s@%s reset levels definitions on channel %s", + s_ChanServ, u->nick, u->username, u->host, ci->name); + notice_lang(s_ChanServ, u, CHAN_LEVELS_RESET, chan); + } else { + syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX); + } + return MOD_CONT; +} |