/* SOP/AOP/VOP handling for ChanServ. * * 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 "conffile.h" #include "language.h" #include "commands.h" #include "modules/nickserv/nickserv.h" #include "modules/operserv/operserv.h" #include "chanserv.h" #include "cs-local.h" /*************************************************************************/ static Module *module; static Module *module_chanserv; /*************************************************************************/ /* Return the name of the list or the syntax message number corresponding * to the given level. Does no error checking. */ # define XOP_LISTNAME(level) \ ((level)==ACCLEV_SOP ? "SOP" : (level)==ACCLEV_AOP ? "AOP" : \ (level)==ACCLEV_HOP ? "HOP" : "VOP") # define XOP_SYNTAX(level) \ ((level)==ACCLEV_SOP ? CHAN_SOP_SYNTAX : \ (level)==ACCLEV_AOP ? CHAN_AOP_SYNTAX : \ (level)==ACCLEV_HOP ? CHAN_HOP_SYNTAX : CHAN_VOP_SYNTAX) /*************************************************************************/ /* Local functions. */ static void handle_xop(User *u, int level); static int xop_del_callback(User *u, int num, va_list args); static int xop_list(User *u, int index, ChannelInfo *ci, int *sent_header, int relindex); static int xop_list_callback(User *u, int num, va_list args); /*************************************************************************/ static void do_sop(User *u); static void do_aop(User *u); static void do_hop(User *u); static void do_vop(User *u); static Command cmds[] = { { "SOP", do_sop, NULL, CHAN_HELP_SOP, -1,-1 }, { "AOP", do_aop, NULL, CHAN_HELP_AOP, -1,-1 }, { "VOP", do_vop, NULL, CHAN_HELP_VOP, -1,-1 }, { NULL } }; static Command cmds_halfop[] = { { "HOP", do_hop, NULL, CHAN_HELP_HOP, -1,-1 }, { NULL } }; /*************************************************************************/ /*************************** The *OP commands ****************************/ /*************************************************************************/ /* SOP, VOP, AOP, and HOP wrappers. These just call handle_xop() with the * appropriate level. */ static void do_sop(User *u) { handle_xop(u, ACCLEV_SOP); } static void do_aop(User *u) { handle_xop(u, ACCLEV_AOP); } static void do_hop(User *u) { handle_xop(u, ACCLEV_HOP); } static void do_vop(User *u) { handle_xop(u, ACCLEV_VOP); } /*************************************************************************/ /* Central handler for all *OP commands. */ static void handle_xop(User *u, int level) { char *chan = strtok(NULL, " "); char *cmd = strtok(NULL, " "); char *nick = strtok(NULL, " "); ChannelInfo *ci; NickInfo *ni; NickGroupInfo *ngi; int i; const char *listname = XOP_LISTNAME(level); int is_list = (cmd && (stricmp(cmd,"LIST")==0 || stricmp(cmd,"COUNT")==0)); int is_servadmin = is_services_admin(u); if (!cmd || (!is_list && !nick) || (stricmp(cmd,"COUNT")==0 && nick)) { syntax_error(s_ChanServ, u, listname, XOP_SYNTAX(level)); } else if (!(ci = get_channelinfo(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 (!is_servadmin && !check_access_cmd(u, ci, "ACCESS", is_list ? "LIST" : cmd)) { notice_lang(s_ChanServ, u, ACCESS_DENIED); } else if (stricmp(cmd, "ADD") == 0) { if (readonly) { notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED); return; } switch (access_add(ci, nick, level, is_servadmin ? ACCLEV_FOUNDER : get_access(u,ci))) { case RET_ADDED: notice_lang(s_ChanServ, u, CHAN_XOP_ADDED, nick, chan, listname); break; case RET_CHANGED: notice_lang(s_ChanServ, u, CHAN_XOP_LEVEL_CHANGED, nick, chan, listname); break; case RET_UNCHANGED: notice_lang(s_ChanServ, u, CHAN_XOP_LEVEL_UNCHANGED, nick, chan, listname); break; case RET_LISTFULL: notice_lang(s_ChanServ, u, CHAN_XOP_REACHED_LIMIT, CSAccessMax); break; case RET_NOSUCHNICK: notice_lang(s_ChanServ, u, CHAN_XOP_NICKS_ONLY); break; case RET_NICKFORBID: notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, nick); break; case RET_PERMISSION: notice_lang(s_ChanServ, u, PERMISSION_DENIED); break; } } else if (stricmp(cmd, "DEL") == 0) { if (!is_servadmin && level >= get_access(u, ci)) { notice_lang(s_ChanServ, u, PERMISSION_DENIED); return; } else if (readonly) { if (is_servadmin) { notice_lang(s_ChanServ, u, READ_ONLY_MODE); } else { notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED); return; } } if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) { int count, deleted, last; int offset = 0; /* used internally by the deletion routine. */ deleted = process_numlist(nick, &count, xop_del_callback, u, ci, &offset, &last, level); if (!deleted) { if (count == 1) { notice_lang(s_ChanServ, u, CHAN_XOP_NO_SUCH_ENTRY, last, ci->name, listname); } else { notice_lang(s_ChanServ, u, CHAN_XOP_NO_MATCH, ci->name, listname); } return; /* don't bother with put_channelinfo() */ } else if (deleted == 1) { notice_lang(s_ChanServ, u, CHAN_XOP_DELETED_ONE, ci->name, listname); } else { notice_lang(s_ChanServ, u, CHAN_XOP_DELETED_SEVERAL, deleted, ci->name, listname); } } else { ni = get_nickinfo(nick); if (!ni) { notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, nick); return; } if (!(ngi = get_ngi(ni))) { notice_lang(s_ChanServ, u, INTERNAL_ERROR); return; } ARRAY_SEARCH_SCALAR(ci->access, nickgroup, ngi->id, i); if (i == ci->access_count || ci->access[i].level != level) { notice_lang(s_ChanServ, u, CHAN_XOP_NOT_FOUND, nick, chan, listname); return; } ci->access[i].nickgroup = 0; notice_lang(s_ChanServ, u, CHAN_XOP_DELETED, ngi_mainnick(ngi), ci->name, listname); } put_channelinfo(ci); } else if (stricmp(cmd, "LIST") == 0) { int sent_header = 0; int relindex = 0; if (ci->access_count == 0) { notice_lang(s_ChanServ, u, CHAN_XOP_LIST_EMPTY, chan, listname); return; } if (nick && strspn(nick, "1234567890,-") == strlen(nick)) { process_numlist(nick, NULL, xop_list_callback, u, ci, &sent_header, level); } else { ARRAY_FOREACH (i, ci->access) { if (!ci->access[i].nickgroup || ci->access[i].level != level) continue; relindex++; if (nick && ci->access[i].nickgroup) { if (!(ngi = get_ngi_id(ci->access[i].nickgroup)) || !match_wild_nocase(nick, ngi_mainnick(ngi)) ) { continue; } } xop_list(u, i, ci, &sent_header, relindex); } } if (!sent_header) notice_lang(s_ChanServ, u, CHAN_XOP_NO_MATCH, chan, listname); } else if (stricmp(cmd, "COUNT") == 0) { int count = 0; if (ci->access_count == 0) { notice_lang(s_ChanServ, u, CHAN_XOP_LIST_EMPTY, chan, listname); return; } ARRAY_FOREACH (i, ci->access) { if (ci->access[i].nickgroup && ci->access[i].level == level) count++; } if (count) notice_lang(s_ChanServ, u, CHAN_XOP_COUNT, chan, listname, count); else notice_lang(s_ChanServ, u, CHAN_XOP_LIST_EMPTY, chan, listname); } else { syntax_error(s_ChanServ, u, listname, XOP_SYNTAX(level)); } } /*************************************************************************/ /* Delete the num'th access list entry with access level oplevel. * Note: Permission checking is assumed to have been done. */ static int xop_del_callback(User *u, int num, va_list args) { ChannelInfo *ci = va_arg(args, ChannelInfo *); int *offset = va_arg(args, int *); int *last = va_arg(args, int *); int oplevel = va_arg(args, int); int i; *last = num; num -= *offset; /* Compensate for deleted items */ if (num < 1) return 0; if (num > ci->access_count) return -1; /* Out of range */ ARRAY_FOREACH (i, ci->access) { if (ci->access[i].nickgroup && ci->access[i].level == oplevel) { num--; if (num <= 0) break; } } if (num > 0) return -1; /* Out of range for given access level */ ci->access[i].nickgroup = 0; (*offset)++; return 1; } /*************************************************************************/ static int xop_list(User *u, int index, ChannelInfo *ci, int *sent_header, int relindex) { ChanAccess *access = &ci->access[index]; NickGroupInfo *ngi; if (!(ngi = get_ngi_id(access->nickgroup))) return 0; if (!*sent_header) { notice_lang(s_ChanServ, u, CHAN_XOP_LIST_HEADER, XOP_LISTNAME(access->level), ci->name); *sent_header = 1; } notice_lang(s_ChanServ, u, CHAN_XOP_LIST_FORMAT, relindex, ngi_mainnick(ngi)); return 1; } static int xop_list_callback(User *u, int num, va_list args) { ChannelInfo *ci = va_arg(args, ChannelInfo *); int *sent_header = va_arg(args, int *); int oplevel = va_arg(args, int); int i; if (num < 1 || num > ci->access_count) return 0; ARRAY_FOREACH (i, ci->access) { if (ci->access[i].nickgroup && ci->access[i].level == oplevel) { num--; if (num <= 0) break; } } return num>0 ? 0 : xop_list(u, i-1, ci, sent_header, num); } /*************************************************************************/ /*************************************************************************/ /* Callback to display help text for SOP and AOP. */ static int do_help(User *u, const char *param) { if (stricmp(param, "SOP") == 0) { notice_help(s_ChanServ, u, CHAN_HELP_SOP); notice_help(s_ChanServ, u, CHAN_HELP_SOP_MID1); notice_help(s_ChanServ, u, CHAN_HELP_SOP_MID2); notice_help(s_ChanServ, u, CHAN_HELP_SOP_END); return 1; } else if (stricmp(param, "AOP") == 0) { notice_help(s_ChanServ, u, CHAN_HELP_AOP); notice_help(s_ChanServ, u, CHAN_HELP_AOP_MID); notice_help(s_ChanServ, u, CHAN_HELP_AOP_END); return 1; } return 0; } /*************************************************************************/ /***************************** Module stuff ******************************/ /*************************************************************************/ const int32 module_version = MODULE_VERSION_CODE; ConfigDirective module_config[] = { { NULL } }; /*************************************************************************/ int init_module(Module *module_) { module = module_; module_chanserv = find_module("chanserv/main"); if (!module_chanserv) { module_log("Main ChanServ module not loaded"); return 0; } use_module(module_chanserv); if (!register_commands(module_chanserv, cmds) || ((protocol_features & PF_HALFOP) && !register_commands(module_chanserv, cmds_halfop)) ) { module_log("Unable to register commands"); exit_module(0); return 0; } if (!add_callback(module_chanserv, "HELP", do_help)) { module_log("Unable to add callbacks"); exit_module(0); return 0; } return 1; } /*************************************************************************/ int exit_module(int shutdown_unused) { #ifdef CLEAN_COMPILE shutdown_unused = shutdown_unused; #endif if (module_chanserv) { remove_callback(module_chanserv, "HELP", do_help); if (protocol_features & PF_HALFOP) unregister_commands(module_chanserv, cmds_halfop); unregister_commands(module_chanserv, cmds); unuse_module(module_chanserv); module_chanserv = NULL; } return 1; } /*************************************************************************/