cs_access.c

Go to the documentation of this file.
00001 /* ChanServ core functions
00002  *
00003  * (C) 2003-2013 Anope Team
00004  * Contact us at team@anope.org
00005  *
00006  * Please read COPYING and README for further details.
00007  *
00008  * Based on the original code of Epona by Lara.
00009  * Based on the original code of Services by Andy Church. 
00010  * 
00011  *
00012  */
00013 /*************************************************************************/
00014 
00015 #include "module.h"
00016 
00017 
00018 static int do_access(User * u);
00019 static int do_levels(User * u);
00020 
00021 static void myChanServHelp(User * u);
00022 
00029 int AnopeInit(int argc, char **argv)
00030 {
00031     Command *c;
00032 
00033     moduleAddAuthor("Anope");
00034     moduleAddVersion
00035         (VERSION_STRING);
00036     moduleSetType(CORE);
00037 
00038     c = createCommand("ACCESS", do_access, NULL, CHAN_HELP_ACCESS, -1, -1,
00039                       -1, -1);
00040     moduleAddCommand(CHANSERV, c, MOD_UNIQUE);
00041     c = createCommand("ACCESS LEVELS", NULL, NULL, CHAN_HELP_ACCESS_LEVELS,
00042                       -1, -1, -1, -1);
00043     moduleAddCommand(CHANSERV, c, MOD_UNIQUE);
00044     c = createCommand("LEVELS", do_levels, NULL, CHAN_HELP_LEVELS, -1, -1,
00045                       -1, -1);
00046     moduleAddCommand(CHANSERV, c, MOD_UNIQUE);
00047     moduleSetChanHelp(myChanServHelp);
00048 
00049     return MOD_CONT;
00050 }
00051 
00055 void AnopeFini(void)
00056 {
00057 
00058 }
00059 
00060 
00061 
00066 static void myChanServHelp(User * u)
00067 {
00068     notice_lang(s_ChanServ, u, CHAN_HELP_CMD_ACCESS);
00069     notice_lang(s_ChanServ, u, CHAN_HELP_CMD_LEVELS);
00070 }
00071 
00072 
00073 static int access_del(User * u, ChannelInfo *ci, ChanAccess * access, int *perm, int uacc)
00074 {
00075         char *nick;
00076     if (!access->in_use)
00077         return 0;
00078     if (!is_services_admin(u) && uacc <= access->level) {
00079         (*perm)++;
00080         return 0;
00081     }
00082     nick = access->nc->display;
00083     access->nc = NULL;
00084     access->in_use = 0;
00085     send_event(EVENT_ACCESS_DEL, 3, ci->name, u->nick, nick);
00086     return 1;
00087 }
00088 
00089 static int access_del_callback(User * u, int num, va_list args)
00090 {
00091     ChannelInfo *ci = va_arg(args, ChannelInfo *);
00092     int *last = va_arg(args, int *);
00093     int *perm = va_arg(args, int *);
00094     int uacc = va_arg(args, int);
00095     if (num < 1 || num > ci->accesscount)
00096         return 0;
00097     *last = num;
00098     return access_del(u, ci, &ci->access[num - 1], perm, uacc);
00099 }
00100 
00101 
00102 static int access_list(User * u, int index, ChannelInfo * ci,
00103                        int *sent_header)
00104 {
00105     ChanAccess *access = &ci->access[index];
00106     const char *xop;
00107 
00108     if (!access->in_use)
00109         return 0;
00110 
00111     if (!*sent_header) {
00112         notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_HEADER, ci->name);
00113         *sent_header = 1;
00114     }
00115 
00116     if (ci->flags & CI_XOP) {
00117         xop = get_xop_level(access->level);
00118         notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_XOP_FORMAT, index + 1,
00119                     xop, access->nc->display);
00120     } else {
00121         notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_AXS_FORMAT, index + 1,
00122                     access->level, access->nc->display);
00123     }
00124     return 1;
00125 }
00126 
00127 static int access_list_callback(User * u, int num, va_list args)
00128 {
00129     ChannelInfo *ci = va_arg(args, ChannelInfo *);
00130     int *sent_header = va_arg(args, int *);
00131     if (num < 1 || num > ci->accesscount)
00132         return 0;
00133     return access_list(u, num - 1, ci, sent_header);
00134 }
00135 
00136 
00142 static int do_access(User * u)
00143 {
00144     char *chan = strtok(NULL, " ");
00145     char *cmd = strtok(NULL, " ");
00146     char *nick = strtok(NULL, " ");
00147     char *s = strtok(NULL, " ");
00148     char event_access[BUFSIZE];
00149 
00150     ChannelInfo *ci;
00151     NickAlias *na = NULL;
00152     NickCore *nc;
00153     ChanAccess *access;
00154 
00155     int i;
00156     int level = 0, ulev;
00157     int is_clear = (cmd && stricmp(cmd, "CLEAR") == 0);
00158     int is_list = (cmd && stricmp(cmd, "LIST") == 0);
00159     int is_servadmin = is_services_admin(u);
00160 
00161     /* If LIST, we don't *require* any parameters, but we can take any.
00162      * If DEL, we require a nick and no level.
00163      * Else (ADD), we require a level (which implies a nick). */
00164     if (!cmd || ((is_list || is_clear) ? 0 :
00165                  (stricmp(cmd, "DEL") == 0) ? (!nick || s) : !s)) {
00166         syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_SYNTAX);
00167     } else if (!(ci = cs_findchan(chan))) {
00168         notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
00169     } else if (ci->flags & CI_VERBOTEN) {
00170         notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
00171         /* We still allow LIST and CLEAR in xOP mode, but not others */
00172     } else if ((ci->flags & CI_XOP) && !is_list && !is_clear) {
00173                 if (ircd->halfop)
00174                 notice_lang(s_ChanServ, u, CHAN_ACCESS_XOP_HOP, s_ChanServ);
00175                 else
00176                 notice_lang(s_ChanServ, u, CHAN_ACCESS_XOP, s_ChanServ);
00177     } else if (((is_list && !check_access(u, ci, CA_ACCESS_LIST))
00178                 || (!is_list && !check_access(u, ci, CA_ACCESS_CHANGE)))
00179                && !is_servadmin) {
00180         notice_lang(s_ChanServ, u, ACCESS_DENIED);
00181     } else if (stricmp(cmd, "ADD") == 0) {
00182         if (readonly) {
00183             notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
00184             return MOD_CONT;
00185         }
00186 
00187         level = atoi(s);
00188         ulev = get_access(u, ci);
00189 
00190         if (!is_servadmin && level >= ulev) {
00191             notice_lang(s_ChanServ, u, PERMISSION_DENIED);
00192             return MOD_CONT;
00193         }
00194 
00195         if (level == 0) {
00196             notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_NONZERO);
00197             return MOD_CONT;
00198         } else if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER) {
00199             notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_RANGE,
00200                         ACCESS_INVALID + 1, ACCESS_FOUNDER - 1);
00201             return MOD_CONT;
00202         }
00203 
00204         na = findnick(nick);
00205         if (!na) {
00206             notice_lang(s_ChanServ, u, CHAN_ACCESS_NICKS_ONLY);
00207             return MOD_CONT;
00208         }
00209         if (na->status & NS_VERBOTEN) {
00210             notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, nick);
00211             return MOD_CONT;
00212         }
00213 
00214         nc = na->nc;
00215         for (access = ci->access, i = 0; i < ci->accesscount;
00216              access++, i++) {
00217             if (access->nc == nc) {
00218                 /* Don't allow lowering from a level >= ulev */
00219                 if (!is_servadmin && access->level >= ulev) {
00220                     notice_lang(s_ChanServ, u, PERMISSION_DENIED);
00221                     return MOD_CONT;
00222                 }
00223                 if (access->level == level) {
00224                     notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_UNCHANGED,
00225                                 access->nc->display, chan, level);
00226                     return MOD_CONT;
00227                 }
00228                 access->level = level;
00229                 snprintf(event_access, BUFSIZE, "%d", access->level);
00230                 send_event(EVENT_ACCESS_CHANGE, 4, ci->name, u->nick,
00231                            na->nick, event_access);
00232                 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);
00233                 notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_CHANGED,
00234                             access->nc->display, chan, level);
00235                 return MOD_CONT;
00236             }
00237         }
00238 
00239         if (i < CSAccessMax) {
00240             ci->accesscount++;
00241             ci->access =
00242                 srealloc(ci->access,
00243                         sizeof(ChanAccess) * ci->accesscount);
00244         } else {
00245             notice_lang(s_ChanServ, u, CHAN_ACCESS_REACHED_LIMIT,
00246                         CSAccessMax);
00247             return MOD_CONT;
00248         }
00249 
00250         access = &ci->access[i];
00251         access->nc = nc;
00252         access->in_use = 1;
00253         access->level = level;
00254         access->last_seen = 0;
00255 
00256         snprintf(event_access, BUFSIZE, "%d", access->level);
00257         send_event(EVENT_ACCESS_ADD, 4, ci->name, u->nick, na->nick,
00258                    event_access);
00259         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);
00260         notice_lang(s_ChanServ, u, CHAN_ACCESS_ADDED, nc->display,
00261                     ci->name, access->level);
00262     } else if (stricmp(cmd, "DEL") == 0) {
00263         int deleted;
00264         
00265         if (readonly) {
00266             notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
00267             return MOD_CONT;
00268         }
00269 
00270         if (ci->accesscount == 0) {
00271             notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, chan);
00272             return MOD_CONT;
00273         }
00274 
00275         /* Clean the access list to make sure every thing is in use */
00276         CleanAccess(ci);
00277 
00278         /* Special case: is it a number/list?  Only do search if it isn't. */
00279         if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) {
00280             int count, last = -1, perm = 0;
00281             deleted = process_numlist(nick, &count, access_del_callback, u,
00282                                       ci, &last, &perm, get_access(u, ci));
00283             if (!deleted) {
00284                 if (perm) {
00285                     notice_lang(s_ChanServ, u, PERMISSION_DENIED);
00286                 } else if (count == 1) {
00287                     last = atoi(nick);
00288                     notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_SUCH_ENTRY,
00289                                 last, ci->name);
00290                 } else {
00291                     notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_MATCH,
00292                                 ci->name);
00293                 }
00294                         } else {
00295                                 alog("%s: %s!%s@%s (level %d) deleted access of user%s %s on %s",
00296                                         s_ChanServ, u->nick, u->username, u->host, get_access(u, ci), (deleted == 1 ? "" : "s"), nick, chan);
00297                                 if (deleted == 1)
00298                                         notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED_ONE,
00299                             ci->name);
00300                                 else
00301                                         notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED_SEVERAL,
00302                                                                 deleted, ci->name);
00303                         }
00304         } else {
00305             na = findnick(nick);
00306             if (!na) {
00307                 notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, nick);
00308                 return MOD_CONT;
00309             }
00310             nc = na->nc;
00311             for (i = 0; i < ci->accesscount; i++) {
00312                 if (ci->access[i].nc == nc)
00313                     break;
00314             }
00315             if (i == ci->accesscount) {
00316                 notice_lang(s_ChanServ, u, CHAN_ACCESS_NOT_FOUND, nick,
00317                             chan);
00318                 return MOD_CONT;
00319             }
00320             access = &ci->access[i];
00321             if (!is_servadmin && get_access(u, ci) <= access->level) {
00322                 deleted = 0;
00323                 notice_lang(s_ChanServ, u, PERMISSION_DENIED);
00324             } else {
00325                 notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED,
00326                             access->nc->display, ci->name);
00327                 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);
00328                 access->nc = NULL;
00329                 access->in_use = 0;
00330                 deleted = 1;
00331             }
00332         }
00333 
00334         CleanAccess(ci);
00335 
00336             /* We don't know the nick if someone used numbers, so we trigger the event without
00337              * nick param. We just do this once, even if someone enters a range. -Certus */
00338             if (na)
00339                 send_event(EVENT_ACCESS_DEL, 3, ci->name, u->nick, na->nick);
00340             else
00341                 send_event(EVENT_ACCESS_DEL, 2, ci->name, u->nick);
00342     } else if (stricmp(cmd, "LIST") == 0) {
00343         int sent_header = 0;
00344 
00345         if (ci->accesscount == 0) {
00346             notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, chan);
00347             return MOD_CONT;
00348         }
00349 
00350         CleanAccess(ci);        
00351 
00352         if (nick && strspn(nick, "1234567890,-") == strlen(nick)) {
00353             process_numlist(nick, NULL, access_list_callback, u, ci,
00354                             &sent_header);
00355         } else {
00356             for (i = 0; i < ci->accesscount; i++) {
00357                 if (nick && ci->access[i].nc
00358                     && !match_wild_nocase(nick, ci->access[i].nc->display))
00359                     continue;
00360                 access_list(u, i, ci, &sent_header);
00361             }
00362         }
00363         if (!sent_header) {
00364             notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_MATCH, chan);
00365         } else {
00366             notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_FOOTER, ci->name);
00367         }
00368     } else if (stricmp(cmd, "CLEAR") == 0) {
00369 
00370         if (readonly) {
00371             notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
00372             return MOD_CONT;
00373         }
00374 
00375         if (!is_servadmin && !is_founder(u, ci)) {
00376             notice_lang(s_ChanServ, u, PERMISSION_DENIED);
00377             return MOD_CONT;
00378         }
00379 
00380         free(ci->access);
00381         ci->access = NULL;
00382         ci->accesscount = 0;
00383         
00384         send_event(EVENT_ACCESS_CLEAR, 2, ci->name, u->nick);
00385 
00386         notice_lang(s_ChanServ, u, CHAN_ACCESS_CLEAR, ci->name);
00387         alog("%s: %s!%s@%s (level %d) cleared access list on %s",
00388              s_ChanServ, u->nick, u->username, u->host,
00389              get_access(u, ci), chan);
00390 
00391     } else {
00392         syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_SYNTAX);
00393     }
00394     return MOD_CONT;
00395 }
00396 
00397 
00398 static int do_levels(User * u)
00399 {
00400     char *chan = strtok(NULL, " ");
00401     char *cmd = strtok(NULL, " ");
00402     char *what = strtok(NULL, " ");
00403     char *s = strtok(NULL, " ");
00404     char *error;
00405 
00406     ChannelInfo *ci;
00407     int level;
00408     int i;
00409 
00410     /* If SET, we want two extra parameters; if DIS[ABLE] or FOUNDER, we want only
00411      * one; else, we want none.
00412      */
00413     if (!cmd
00414         || ((stricmp(cmd, "SET") == 0) ? !s
00415             : ((strnicmp(cmd, "DIS", 3) == 0)) ? (!what || s) : !!what)) {
00416         syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX);
00417     } else if (!(ci = cs_findchan(chan))) {
00418         notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
00419     } else if (ci->flags & CI_VERBOTEN) {
00420         notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
00421     } else if (ci->flags & CI_XOP) {
00422         notice_lang(s_ChanServ, u, CHAN_LEVELS_XOP);
00423     } else if (!is_founder(u, ci) && !is_services_admin(u)) {
00424         notice_lang(s_ChanServ, u, ACCESS_DENIED);
00425     } else if (stricmp(cmd, "SET") == 0) {
00426         level = strtol(s, &error, 10);
00427 
00428         if (*error != '\0') {
00429             syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX);
00430             return MOD_CONT;
00431         }
00432 
00433         if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER) {
00434             notice_lang(s_ChanServ, u, CHAN_LEVELS_RANGE,
00435                         ACCESS_INVALID + 1, ACCESS_FOUNDER - 1);
00436             return MOD_CONT;
00437         }
00438 
00439         for (i = 0; levelinfo[i].what >= 0; i++) {
00440             if (stricmp(levelinfo[i].name, what) == 0) {
00441                 ci->levels[levelinfo[i].what] = level;
00442 
00443                 alog("%s: %s!%s@%s set level %s on channel %s to %d",
00444                      s_ChanServ, u->nick, u->username, u->host,
00445                      levelinfo[i].name, ci->name, level);
00446                 notice_lang(s_ChanServ, u, CHAN_LEVELS_CHANGED,
00447                             levelinfo[i].name, chan, level);
00448                 return MOD_CONT;
00449             }
00450         }
00451 
00452         notice_lang(s_ChanServ, u, CHAN_LEVELS_UNKNOWN, what, s_ChanServ);
00453 
00454     } else if (stricmp(cmd, "DIS") == 0 || stricmp(cmd, "DISABLE") == 0) {
00455         for (i = 0; levelinfo[i].what >= 0; i++) {
00456             if (stricmp(levelinfo[i].name, what) == 0) {
00457                 ci->levels[levelinfo[i].what] = ACCESS_INVALID;
00458 
00459                 alog("%s: %s!%s@%s disabled level %s on channel %s",
00460                      s_ChanServ, u->nick, u->username, u->host,
00461                      levelinfo[i].name, ci->name);
00462                 notice_lang(s_ChanServ, u, CHAN_LEVELS_DISABLED,
00463                             levelinfo[i].name, chan);
00464                 return MOD_CONT;
00465             }
00466         }
00467 
00468         notice_lang(s_ChanServ, u, CHAN_LEVELS_UNKNOWN, what, s_ChanServ);
00469     } else if (stricmp(cmd, "LIST") == 0) {
00470         int i;
00471 
00472         notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_HEADER, chan);
00473 
00474         if (!levelinfo_maxwidth) {
00475             for (i = 0; levelinfo[i].what >= 0; i++) {
00476                 int len = strlen(levelinfo[i].name);
00477                 if (len > levelinfo_maxwidth)
00478                     levelinfo_maxwidth = len;
00479             }
00480         }
00481 
00482         for (i = 0; levelinfo[i].what >= 0; i++) {
00483             int j = ci->levels[levelinfo[i].what];
00484 
00485             if (j == ACCESS_INVALID) {
00486                 j = levelinfo[i].what;
00487 
00488                 if (j == CA_AUTOOP || j == CA_AUTODEOP || j == CA_AUTOVOICE
00489                     || j == CA_NOJOIN) {
00490                     notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_DISABLED,
00491                                 levelinfo_maxwidth, levelinfo[i].name);
00492                 } else {
00493                     notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_DISABLED,
00494                                 levelinfo_maxwidth, levelinfo[i].name);
00495                 }
00496             } else if (j == ACCESS_FOUNDER) {
00497                 notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_FOUNDER,
00498                             levelinfo_maxwidth, levelinfo[i].name);
00499             } else {
00500                 notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_NORMAL,
00501                             levelinfo_maxwidth, levelinfo[i].name, j);
00502             }
00503         }
00504 
00505     } else if (stricmp(cmd, "RESET") == 0) {
00506         reset_levels(ci);
00507 
00508         alog("%s: %s!%s@%s reset levels definitions on channel %s",
00509              s_ChanServ, u->nick, u->username, u->host, ci->name);
00510         notice_lang(s_ChanServ, u, CHAN_LEVELS_RESET, chan);
00511     } else {
00512         syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX);
00513     }
00514     return MOD_CONT;
00515 }