cs_access.cpp

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 #include "module.h"
00015 
00016 static std::map<Anope::string, int16_t, ci::less> defaultLevels;
00017 
00018 static inline void reset_levels(ChannelInfo *ci)
00019 {
00020         ci->ClearLevels();
00021         for (std::map<Anope::string, int16_t, ci::less>::iterator it = defaultLevels.begin(), it_end = defaultLevels.end(); it != it_end; ++it)
00022                 ci->SetLevel(it->first, it->second);
00023 }
00024 
00025 class AccessChanAccess : public ChanAccess
00026 {
00027  public:
00028         int level;
00029 
00030         AccessChanAccess(AccessProvider *p) : ChanAccess(p)
00031         {
00032         }
00033 
00034         bool HasPriv(const Anope::string &name) const anope_override
00035         {
00036                 return this->ci->GetLevel(name) != ACCESS_INVALID && this->level >= this->ci->GetLevel(name);
00037         }
00038 
00039         Anope::string AccessSerialize() const
00040         {
00041                 return stringify(this->level);
00042         }
00043 
00044         void AccessUnserialize(const Anope::string &data) anope_override
00045         {
00046                 this->level = convertTo<int>(data);
00047         }
00048 
00049         static int DetermineLevel(const ChanAccess *access)
00050         {
00051                 if (access->provider->name == "access/access")
00052                 {
00053                         const AccessChanAccess *aaccess = anope_dynamic_static_cast<const AccessChanAccess *>(access);
00054                         return aaccess->level;
00055                 }
00056                 else
00057                 {
00058                         int highest = 1;
00059                         const std::vector<Privilege> &privs = PrivilegeManager::GetPrivileges();
00060 
00061                         for (unsigned i = 0; i < privs.size(); ++i)
00062                         {
00063                                 const Privilege &p = privs[i];
00064                                 if (access->ci->GetLevel(p.name) > highest && access->HasPriv(p.name))
00065                                         highest = access->ci->GetLevel(p.name);
00066                         }
00067 
00068                         if (highest >= ACCESS_FOUNDER)
00069                                 highest = ACCESS_FOUNDER - 1;
00070 
00071                         return highest;
00072                 }
00073         }
00074 };
00075 
00076 class AccessAccessProvider : public AccessProvider
00077 {
00078  public:
00079         AccessAccessProvider(Module *o) : AccessProvider(o, "access/access")
00080         {
00081         }
00082 
00083         ChanAccess *Create() anope_override
00084         {
00085                 return new AccessChanAccess(this);
00086         }
00087 };
00088 
00089 class CommandCSAccess : public Command
00090 {
00091         void DoAdd(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
00092         {
00093                 Anope::string mask = params[2];
00094                 int level = ACCESS_INVALID;
00095 
00096                 try
00097                 {
00098                         level = convertTo<int>(params[3]);
00099                 }
00100                 catch (const ConvertException &) { }
00101 
00102                 if (!level)
00103                 {
00104                         source.Reply(_("Access level must be non-zero."));
00105                         return;
00106                 }
00107                 else if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER)
00108                 {
00109                         source.Reply(CHAN_ACCESS_LEVEL_RANGE, ACCESS_INVALID + 1, ACCESS_FOUNDER - 1);
00110                         return;
00111                 }
00112 
00113                 AccessGroup u_access = source.AccessFor(ci);
00114                 const ChanAccess *highest = u_access.Highest();
00115 
00116                 AccessChanAccess tmp_access(NULL);
00117                 tmp_access.ci = ci;
00118                 tmp_access.level = level;
00119 
00120                 bool override = false;
00121 
00122                 if ((!highest || *highest <= tmp_access) && !u_access.founder)
00123                 {
00124                         if (source.HasPriv("chanserv/access/modify"))
00125                                 override = true;
00126                         else
00127                         {
00128                                 source.Reply(ACCESS_DENIED);
00129                                 return;
00130                         }
00131                 }
00132 
00133                 if (mask.find_first_of("!*@") == Anope::string::npos && !NickAlias::Find(mask))
00134                 {
00135                         User *targ = User::Find(mask, true);
00136                         if (targ != NULL)
00137                                 mask = "*!*@" + targ->GetDisplayedHost();
00138                         else
00139                         {
00140                                 source.Reply(NICK_X_NOT_REGISTERED, mask.c_str());
00141                                 return;
00142                         }
00143                 }
00144 
00145                 for (unsigned i = ci->GetAccessCount(); i > 0; --i)
00146                 {
00147                         const ChanAccess *access = ci->GetAccess(i - 1);
00148                         if (mask.equals_ci(access->mask))
00149                         {
00150                                 /* Don't allow lowering from a level >= u_level */
00151                                 if ((!highest || *access >= *highest) && !u_access.founder && !source.HasPriv("chanserv/access/modify"))
00152                                 {
00153                                         source.Reply(ACCESS_DENIED);
00154                                         return;
00155                                 }
00156                                 ci->EraseAccess(i - 1);
00157                                 break;
00158                         }
00159                 }
00160 
00161                 if (ci->GetAccessCount() >= Config->CSAccessMax)
00162                 {
00163                         source.Reply(_("Sorry, you can only have %d access entries on a channel."), Config->CSAccessMax);
00164                         return;
00165                 }
00166 
00167                 ServiceReference<AccessProvider> provider("AccessProvider", "access/access");
00168                 if (!provider)
00169                         return;
00170                 AccessChanAccess *access = anope_dynamic_static_cast<AccessChanAccess *>(provider->Create());
00171                 access->ci = ci;
00172                 access->mask = mask;
00173                 access->creator = source.GetNick();
00174                 access->level = level;
00175                 access->last_seen = 0;
00176                 access->created = Anope::CurTime;
00177                 ci->AddAccess(access);
00178 
00179                 FOREACH_MOD(I_OnAccessAdd, OnAccessAdd(ci, source, access));
00180 
00181                 Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to add " << mask << " with level " << level;
00182                 source.Reply(_("\002%s\002 added to %s access list at level \002%d\002."), access->mask.c_str(), ci->name.c_str(), level);
00183 
00184                 return;
00185         }
00186 
00187         void DoDel(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
00188         {
00189                 Anope::string mask = params[2];
00190 
00191                 if (!isdigit(mask[0]) && mask.find_first_of("!*@") == Anope::string::npos && !NickAlias::Find(mask))
00192                 {
00193                         User *targ = User::Find(mask, true);
00194                         if (targ != NULL)
00195                                 mask = "*!*@" + targ->GetDisplayedHost();
00196                         else
00197                         {
00198                                 source.Reply(NICK_X_NOT_REGISTERED, mask.c_str());
00199                                 return;
00200                         }
00201                 }
00202 
00203                 if (!ci->GetAccessCount())
00204                         source.Reply(_("%s access list is empty."), ci->name.c_str());
00205                 else if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos)
00206                 {
00207                         class AccessDelCallback : public NumberList
00208                         {
00209                                 CommandSource &source;
00210                                 ChannelInfo *ci;
00211                                 Command *c;
00212                                 unsigned deleted;
00213                                 Anope::string Nicks;
00214                                 bool Denied;
00215                                 bool override;
00216                          public:
00217                                 AccessDelCallback(CommandSource &_source, ChannelInfo *_ci, Command *_c, const Anope::string &numlist) : NumberList(numlist, true), source(_source), ci(_ci), c(_c), deleted(0), Denied(false), override(false)
00218                                 {
00219                                         if (!source.AccessFor(ci).HasPriv("ACCESS_CHANGE") && source.HasPriv("chanserv/access/modify"))
00220                                                 this->override = true;
00221                                 }
00222 
00223                                 ~AccessDelCallback()
00224                                 {
00225                                         if (Denied && !deleted)
00226                                                 source.Reply(ACCESS_DENIED);
00227                                         else if (!deleted)
00228                                                 source.Reply(_("No matching entries on %s access list."), ci->name.c_str());
00229                                         else
00230                                         {
00231                                                 Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, c, ci) << "to delete " << Nicks;
00232 
00233                                                 if (deleted == 1)
00234                                                         source.Reply(_("Deleted 1 entry from %s access list."), ci->name.c_str());
00235                                                 else
00236                                                         source.Reply(_("Deleted %d entries from %s access list."), deleted, ci->name.c_str());
00237                                         }
00238                                 }
00239 
00240                                 void HandleNumber(unsigned Number) anope_override
00241                                 {
00242                                         if (!Number || Number > ci->GetAccessCount())
00243                                                 return;
00244 
00245                                         ChanAccess *access = ci->GetAccess(Number - 1);
00246 
00247                                         AccessGroup u_access = source.AccessFor(ci);
00248                                         const ChanAccess *u_highest = u_access.Highest();
00249 
00250                                         if ((!u_highest || *u_highest <= *access) && !u_access.founder && !this->override && !access->mask.equals_ci(source.nc->display))
00251                                         {
00252                                                 Denied = true;
00253                                                 return;
00254                                         }
00255 
00256                                         ++deleted;
00257                                         if (!Nicks.empty())
00258                                                 Nicks += ", " + access->mask;
00259                                         else
00260                                                 Nicks = access->mask;
00261 
00262                                         FOREACH_MOD(I_OnAccessDel, OnAccessDel(ci, source, access));
00263 
00264                                         ci->EraseAccess(Number - 1);
00265                                 }
00266                         }
00267                         delcallback(source, ci, this, mask);
00268                         delcallback.Process();
00269                 }
00270                 else
00271                 {
00272                         AccessGroup u_access = source.AccessFor(ci);
00273                         const ChanAccess *highest = u_access.Highest();
00274 
00275                         for (unsigned i = ci->GetAccessCount(); i > 0; --i)
00276                         {
00277                                 ChanAccess *access = ci->GetAccess(i - 1);
00278                                 if (mask.equals_ci(access->mask))
00279                                 {
00280                                         if (!access->mask.equals_ci(source.nc->display) && !u_access.founder && (!highest || *highest <= *access) && !source.HasPriv("chanserv/access/modify"))
00281                                                 source.Reply(ACCESS_DENIED);
00282                                         else
00283                                         {
00284                                                 source.Reply(_("\002%s\002 deleted from %s access list."), access->mask.c_str(), ci->name.c_str());
00285                                                 bool override = !u_access.founder && !u_access.HasPriv("ACCESS_CHANGE") && !access->mask.equals_ci(source.nc->display);
00286                                                 Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << access->mask;
00287 
00288                                                 FOREACH_MOD(I_OnAccessDel, OnAccessDel(ci, source, access));
00289                                                 delete access;
00290                                         }
00291                                         return;
00292                                 }
00293                         }
00294 
00295                         source.Reply(_("\002%s\002 not found on %s access list."), mask.c_str(), ci->name.c_str());
00296                 }
00297 
00298                 return;
00299         }
00300 
00301         void ProcessList(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params, ListFormatter &list)
00302         {
00303                 const Anope::string &nick = params.size() > 2 ? params[2] : "";
00304 
00305                 if (!ci->GetAccessCount())
00306                         source.Reply(_("%s access list is empty."), ci->name.c_str());
00307                 else if (!nick.empty() && nick.find_first_not_of("1234567890,-") == Anope::string::npos)
00308                 {
00309                         class AccessListCallback : public NumberList
00310                         {
00311                                 ListFormatter &list;
00312                                 ChannelInfo *ci;
00313 
00314                          public:
00315                                 AccessListCallback(ListFormatter &_list, ChannelInfo *_ci, const Anope::string &numlist) : NumberList(numlist, false), list(_list), ci(_ci)
00316                                 {
00317                                 }
00318 
00319                                 void HandleNumber(unsigned number) anope_override
00320                                 {
00321                                         if (!number || number > ci->GetAccessCount())
00322                                                 return;
00323 
00324                                         const ChanAccess *access = ci->GetAccess(number - 1);
00325 
00326                                         Anope::string timebuf;
00327                                         if (ci->c)
00328                                                 for (Channel::ChanUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end; ++cit)
00329                                                         if (access->Matches((*cit)->user, (*cit)->user->Account()))
00330                                                                 timebuf = "Now";
00331                                         if (timebuf.empty())
00332                                         {
00333                                                 if (access->last_seen == 0)
00334                                                         timebuf = "Never";
00335                                                 else
00336                                                         timebuf = Anope::strftime(access->last_seen, NULL, true);
00337                                         }
00338 
00339                                         ListFormatter::ListEntry entry;
00340                                         entry["Number"] = stringify(number);
00341                                         entry["Level"] = stringify(AccessChanAccess::DetermineLevel(access));
00342                                         entry["Mask"] = access->mask;
00343                                         entry["By"] = access->creator;
00344                                         entry["Last seen"] = timebuf;
00345                                         this->list.AddEntry(entry);
00346                                 }
00347                         }
00348                         nl_list(list, ci, nick);
00349                         nl_list.Process();
00350                 }
00351                 else
00352                 {
00353                         for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i)
00354                         {
00355                                 const ChanAccess *access = ci->GetAccess(i);
00356 
00357                                 if (!nick.empty() && !Anope::Match(access->mask, nick))
00358                                         continue;
00359 
00360                                 Anope::string timebuf;
00361                                 if (ci->c)
00362                                         for (Channel::ChanUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end; ++cit)
00363                                                 if (access->Matches((*cit)->user, (*cit)->user->Account()))
00364                                                         timebuf = "Now";
00365                                 if (timebuf.empty())
00366                                 {
00367                                         if (access->last_seen == 0)
00368                                                 timebuf = "Never";
00369                                         else
00370                                                 timebuf = Anope::strftime(access->last_seen, NULL, true);
00371                                 }
00372 
00373                                 ListFormatter::ListEntry entry;
00374                                 entry["Number"] = stringify(i + 1);
00375                                 entry["Level"] = stringify(AccessChanAccess::DetermineLevel(access));
00376                                 entry["Mask"] = access->mask;
00377                                 entry["By"] = access->creator;
00378                                 entry["Last seen"] = timebuf;
00379                                 list.AddEntry(entry);
00380                         }
00381                 }
00382 
00383                 if (list.IsEmpty())
00384                         source.Reply(_("No matching entries on %s access list."), ci->name.c_str());
00385                 else
00386                 {
00387                         std::vector<Anope::string> replies;
00388                         list.Process(replies);
00389 
00390                         source.Reply(_("Access list for %s:"), ci->name.c_str());
00391         
00392                         for (unsigned i = 0; i < replies.size(); ++i)
00393                                 source.Reply(replies[i]);
00394 
00395                         source.Reply(_("End of access list"));
00396                 }
00397 
00398                 return;
00399         }
00400 
00401         void DoList(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
00402         {
00403                 if (!ci->GetAccessCount())
00404                 {
00405                         source.Reply(_("%s access list is empty."), ci->name.c_str());
00406                         return;
00407                 }
00408 
00409                 ListFormatter list;
00410                 list.AddColumn("Number").AddColumn("Level").AddColumn("Mask");
00411                 this->ProcessList(source, ci, params, list);
00412         }
00413 
00414         void DoView(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
00415         {
00416                 if (!ci->GetAccessCount())
00417                 {
00418                         source.Reply(_("%s access list is empty."), ci->name.c_str());
00419                         return;
00420                 }
00421 
00422                 ListFormatter list;
00423                 list.AddColumn("Number").AddColumn("Level").AddColumn("Mask").AddColumn("By").AddColumn("Last seen");
00424                 this->ProcessList(source, ci, params, list);
00425         }
00426 
00427         void DoClear(CommandSource &source, ChannelInfo *ci)
00428         {
00429                 if (!source.IsFounder(ci) && !source.HasPriv("chanserv/access/modify"))
00430                         source.Reply(ACCESS_DENIED);
00431                 else
00432                 {
00433                         FOREACH_MOD(I_OnAccessClear, OnAccessClear(ci, source));
00434 
00435                         ci->ClearAccess();
00436 
00437                         source.Reply(_("Channel %s access list has been cleared."), ci->name.c_str());
00438 
00439                         bool override = !source.IsFounder(ci);
00440                         Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clear the access list";
00441                 }
00442 
00443                 return;
00444         }
00445 
00446  public:
00447         CommandCSAccess(Module *creator) : Command(creator, "chanserv/access", 2, 4)
00448         {
00449                 this->SetDesc(_("Modify the list of privileged users"));
00450                 this->SetSyntax(_("\037channel\037 ADD \037mask\037 \037level\037"));
00451                 this->SetSyntax(_("\037channel\037 DEL {\037mask\037 | \037entry-num\037 | \037list\037}"));
00452                 this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]"));
00453                 this->SetSyntax(_("\037channel\037 VIEW [\037mask\037 | \037list\037]"));
00454                 this->SetSyntax(_("\037channel\037 CLEAR"));
00455         }
00456 
00457         void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
00458         {
00459                 const Anope::string &cmd = params[1];
00460                 const Anope::string &nick = params.size() > 2 ? params[2] : "";
00461                 const Anope::string &s = params.size() > 3 ? params[3] : "";
00462 
00463                 ChannelInfo *ci = ChannelInfo::Find(params[0]);
00464                 if (ci == NULL)
00465                 {
00466                         source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
00467                         return;
00468                 }
00469 
00470                 bool is_list = cmd.equals_ci("LIST") || cmd.equals_ci("VIEW");
00471                 bool is_clear = cmd.equals_ci("CLEAR");
00472                 bool is_del = cmd.equals_ci("DEL");
00473 
00474                 bool has_access = false;
00475                 if (source.HasPriv("chanserv/access/modify"))
00476                         has_access = true;
00477                 else if (is_list && source.AccessFor(ci).HasPriv("ACCESS_LIST"))
00478                         has_access = true;
00479                 else if (source.AccessFor(ci).HasPriv("ACCESS_CHANGE"))
00480                         has_access = true;
00481                 else if (is_del)
00482                 {
00483                         const NickAlias *na = NickAlias::Find(nick);
00484                         if (na && na->nc == source.GetAccount())
00485                                 has_access = true;
00486                 }
00487 
00488                 /* If LIST, we don't *require* any parameters, but we can take any.
00489                  * If DEL, we require a nick and no level.
00490                  * Else (ADD), we require a level (which implies a nick). */
00491                 if (is_list || is_clear ? 0 : (cmd.equals_ci("DEL") ? (nick.empty() || !s.empty()) : s.empty()))
00492                         this->OnSyntaxError(source, cmd);
00493                 else if (!has_access)
00494                         source.Reply(ACCESS_DENIED);
00495                 else if (Anope::ReadOnly && !is_list)
00496                         source.Reply(_("Sorry, channel access list modification is temporarily disabled."));
00497                 else if (cmd.equals_ci("ADD"))
00498                         this->DoAdd(source, ci, params);
00499                 else if (cmd.equals_ci("DEL"))
00500                         this->DoDel(source, ci, params);
00501                 else if (cmd.equals_ci("LIST"))
00502                         this->DoList(source, ci, params);
00503                 else if (cmd.equals_ci("VIEW"))
00504                         this->DoView(source, ci, params);
00505                 else if (cmd.equals_ci("CLEAR"))
00506                         this->DoClear(source, ci);
00507                 else
00508                         this->OnSyntaxError(source, "");
00509 
00510                 return;
00511         }
00512 
00513         bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
00514         {
00515                 this->SendSyntax(source);
00516                 source.Reply(" ");
00517                 source.Reply(_("Maintains the \002access list\002 for a channel.  The access\n"
00518                                 "list specifies which users are allowed chanop status or\n"
00519                                 "access to %s commands on the channel.  Different\n"
00520                                 "user levels allow for access to different subsets of\n"
00521                                 "privileges. Any registered user not on the access list has\n"
00522                                 "a user level of 0, and any unregistered user has a user level\n"
00523                                 "of -1."), source.service->nick.c_str());
00524                 source.Reply(" ");
00525                 source.Reply(_("The \002ACCESS ADD\002 command adds the given mask to the\n"
00526                                 "access list with the given user level; if the mask is\n"
00527                                 "already present on the list, its access level is changed to\n"
00528                                 "the level specified in the command.  The \037level\037 specified\n"
00529                                 "must be less than that of the user giving the command, and\n"
00530                                 "if the \037mask\037 is already on the access list, the current\n"
00531                                 "access level of that nick must be less than the access level\n"
00532                                 "of the user giving the command. When a user joins the channel\n"
00533                                 "the access they receive is from the highest level entry in the\n"
00534                                 "access list."));
00535                 source.Reply(" ");
00536                 source.Reply(_("The \002ACCESS DEL\002 command removes the given nick from the\n"
00537                                 "access list.  If a list of entry numbers is given, those\n"
00538                                 "entries are deleted.  (See the example for LIST below.)\n"
00539                                 "You may remove yourself from an access list, even if you\n"
00540                                 "do not have access to modify that list otherwise."));
00541                 source.Reply(" ");
00542                 source.Reply(_("The \002ACCESS LIST\002 command displays the access list.  If\n"
00543                                 "a wildcard mask is given, only those entries matching the\n"
00544                                 "mask are displayed.  If a list of entry numbers is given,\n"
00545                                 "only those entries are shown; for example:\n"
00546                                 "   \002ACCESS #channel LIST 2-5,7-9\002\n"
00547                                 "      Lists access entries numbered 2 through 5 and\n"
00548                                 "      7 through 9.\n"
00549                                 " \n"
00550                                 "The \002ACCESS VIEW\002 command displays the access list similar\n"
00551                                 "to \002ACCESS LIST\002 but shows the creator and last used time.\n"
00552                                 " \n"
00553                                 "The \002ACCESS CLEAR\002 command clears all entries of the\n"
00554                                 "access list."));
00555                 source.Reply(_("\002User access levels\002\n"
00556                                 " \n"
00557                                 "By default, the following access levels are defined:\n"
00558                                 " \n"
00559                                 "   \002Founder\002   Full access to %s functions; automatic\n"
00560                                 "                     opping upon entering channel.  Note\n"
00561                                 "                     that only one person may have founder\n"
00562                                 "                     status (it cannot be given using the\n"
00563                                 "                     \002ACCESS\002 command).\n"
00564                                 "   \002     10\002   Access to AKICK command; automatic opping.\n"
00565                                 "   \002      5\002   Automatic opping.\n"
00566                                 "   \002      3\002   Automatic voicing.\n"
00567                                 "   \002      0\002   No special privileges; can be opped by other\n"
00568                                 "                     ops (unless \002secure-ops\002 is set).\n"
00569                                 " \n"
00570                                 "These levels may be changed, or new ones added, using the\n"
00571                                 "\002LEVELS\002 command; type \002%s%s HELP LEVELS\002 for\n"
00572                                 "information."), source.service->nick.c_str(), Config->UseStrictPrivMsgString.c_str(), source.service->nick.c_str());
00573                 return true;
00574         }
00575 };
00576 
00577 class CommandCSLevels : public Command
00578 {
00579         void DoSet(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
00580         {
00581                 const Anope::string &what = params[2];
00582                 const Anope::string &lev = params[3];
00583 
00584                 int level;
00585 
00586                 if (lev.equals_ci("FOUNDER"))
00587                         level = ACCESS_FOUNDER;
00588                 else
00589                 {
00590                         try
00591                         {
00592                                 level = convertTo<int>(lev);
00593                         }
00594                         catch (const ConvertException &)
00595                         {
00596                                 this->OnSyntaxError(source, "SET");
00597                                 return;
00598                         }
00599                 }
00600 
00601                 if (level <= ACCESS_INVALID || level > ACCESS_FOUNDER)
00602                         source.Reply(_("Level must be between %d and %d inclusive."), ACCESS_INVALID + 1, ACCESS_FOUNDER - 1);
00603                 else
00604                 {
00605                         Privilege *p = PrivilegeManager::FindPrivilege(what);
00606                         if (p == NULL)
00607                                 source.Reply(_("Setting \002%s\002 not known.  Type \002%s%s HELP LEVELS\002 for a list of valid settings."), what.c_str(), Config->UseStrictPrivMsgString.c_str(), source.service->nick.c_str());
00608                         else
00609                         {
00610                                 ci->SetLevel(p->name, level);
00611                                 FOREACH_MOD(I_OnLevelChange, OnLevelChange(source, ci, p->name, level));
00612 
00613                                 bool override = !source.AccessFor(ci).HasPriv("FOUNDER");
00614                                 Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to set " << p->name << " to level " << level;
00615 
00616                                 if (level == ACCESS_FOUNDER)
00617                                         source.Reply(_("Level for %s on channel %s changed to founder only."), p->name.c_str(), ci->name.c_str());
00618                                 else
00619                                         source.Reply(_("Level for \002%s\002 on channel %s changed to \002%d\002."), p->name.c_str(), ci->name.c_str(), level);
00620                         }
00621                 }
00622         }
00623 
00624         void DoDisable(CommandSource &source, ChannelInfo *ci, const std::vector<Anope::string> &params)
00625         {
00626                 const Anope::string &what = params[2];
00627 
00628                 /* Don't allow disabling of the founder level. It would be hard to change it back if you dont have access to use this command */
00629                 if (!what.equals_ci("FOUNDER"))
00630                 {
00631                         Privilege *p = PrivilegeManager::FindPrivilege(what);
00632                         if (p != NULL)
00633                         {
00634                                 ci->SetLevel(p->name, ACCESS_INVALID);
00635                                 FOREACH_MOD(I_OnLevelChange, OnLevelChange(source, ci, p->name, ACCESS_INVALID));
00636 
00637                                 bool override = !source.AccessFor(ci).HasPriv("FOUNDER");
00638                                 Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable " << p->name;
00639 
00640                                 source.Reply(_("\002%s\002 disabled on channel %s."), p->name.c_str(), ci->name.c_str());
00641                                 return;
00642                         }
00643                 }
00644 
00645                 source.Reply(_("Setting \002%s\002 not known.  Type \002%s%s HELP LEVELS\002 for a list of valid settings."), what.c_str(), Config->UseStrictPrivMsgString.c_str(), source.service->nick.c_str());
00646 
00647                 return;
00648         }
00649 
00650         void DoList(CommandSource &source, ChannelInfo *ci)
00651         {
00652                 source.Reply(_("Access level settings for channel %s:"), ci->name.c_str());
00653 
00654                 ListFormatter list;
00655                 list.AddColumn("Name").AddColumn("Level");
00656 
00657                 const std::vector<Privilege> &privs = PrivilegeManager::GetPrivileges();
00658 
00659                 for (unsigned i = 0; i < privs.size(); ++i)
00660                 {
00661                         const Privilege &p = privs[i];
00662                         int16_t j = ci->GetLevel(p.name);
00663 
00664                         ListFormatter::ListEntry entry;
00665                         entry["Name"] = p.name;
00666 
00667                         if (j == ACCESS_INVALID)
00668                                 entry["Level"] = "(disabled)";
00669                         else if (j == ACCESS_FOUNDER)
00670                                 entry["Level"] = "(founder only)";
00671                         else
00672                                 entry["Level"] = stringify(j);
00673 
00674                         list.AddEntry(entry);
00675                 }
00676 
00677                 std::vector<Anope::string> replies;
00678                 list.Process(replies);
00679 
00680                 for (unsigned i = 0; i < replies.size(); ++i)
00681                         source.Reply(replies[i]);
00682         }
00683 
00684         void DoReset(CommandSource &source, ChannelInfo *ci)
00685         {
00686                 reset_levels(ci);
00687                 FOREACH_MOD(I_OnLevelChange, OnLevelChange(source, ci, "ALL", 0));
00688 
00689                 bool override = !source.AccessFor(ci).HasPriv("FOUNDER");
00690                 Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to reset all levels";
00691 
00692                 source.Reply(_("Access levels for \002%s\002 reset to defaults."), ci->name.c_str());
00693                 return;
00694         }
00695 
00696  public:
00697         CommandCSLevels(Module *creator) : Command(creator, "chanserv/levels", 2, 4)
00698         {
00699                 this->SetDesc(_("Redefine the meanings of access levels"));
00700                 this->SetSyntax(_("\037channel\037 SET \037type\037 \037level\037"));
00701                 this->SetSyntax(_("\037channel\037 {DIS | DISABLE} \037type\037"));
00702                 this->SetSyntax(_("\037channel\037 LIST"));
00703                 this->SetSyntax(_("\037channel\037 RESET"));
00704         }
00705 
00706         void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
00707         {
00708                 const Anope::string &cmd = params[1];
00709                 const Anope::string &what = params.size() > 2 ? params[2] : "";
00710                 const Anope::string &s = params.size() > 3 ? params[3] : "";
00711 
00712                 ChannelInfo *ci = ChannelInfo::Find(params[0]);
00713                 if (ci == NULL)
00714                 {
00715                         source.Reply(CHAN_X_NOT_REGISTERED, params[0].c_str());
00716                         return;
00717                 }
00718 
00719                 /* If SET, we want two extra parameters; if DIS[ABLE] or FOUNDER, we want only
00720                  * one; else, we want none.
00721                  */
00722                 if (cmd.equals_ci("SET") ? s.empty() : (cmd.substr(0, 3).equals_ci("DIS") ? (what.empty() || !s.empty()) : !what.empty()))
00723                         this->OnSyntaxError(source, cmd);
00724                 else if (!source.AccessFor(ci).HasPriv("FOUNDER") && !source.HasPriv("chanserv/access/modify"))
00725                         source.Reply(ACCESS_DENIED);
00726                 else if (cmd.equals_ci("SET"))
00727                         this->DoSet(source, ci, params);
00728                 else if (cmd.equals_ci("DIS") || cmd.equals_ci("DISABLE"))
00729                         this->DoDisable(source, ci, params);
00730                 else if (cmd.equals_ci("LIST"))
00731                         this->DoList(source, ci);
00732                 else if (cmd.equals_ci("RESET"))
00733                         this->DoReset(source, ci);
00734                 else
00735                         this->OnSyntaxError(source, "");
00736 
00737                 return;
00738         }
00739 
00740         bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
00741         {
00742                 if (subcommand.equals_ci("DESC"))
00743                 {
00744                         source.Reply(_("The following feature/function names are available:"));
00745 
00746                         ListFormatter list;
00747                         list.AddColumn("Name").AddColumn("Description");
00748 
00749                         const std::vector<Privilege> &privs = PrivilegeManager::GetPrivileges();
00750                         for (unsigned i = 0; i < privs.size(); ++i)
00751                         {
00752                                 const Privilege &p = privs[i];
00753                                 ListFormatter::ListEntry entry;
00754                                 entry["Name"] = p.name;
00755                                 entry["Description"] = Language::Translate(source.nc, p.desc.c_str());
00756                                 list.AddEntry(entry);
00757                         }
00758 
00759                         std::vector<Anope::string> replies;
00760                         list.Process(replies);
00761 
00762                         for (unsigned i = 0; i < replies.size(); ++i)
00763                                 source.Reply(replies[i]);
00764                 }
00765                 else
00766                 {
00767                         this->SendSyntax(source);
00768                         source.Reply(" ");
00769                         source.Reply(_("The \002LEVELS\002 command allows fine control over the meaning of\n"
00770                                         "the numeric access levels used for channels.  With this\n"
00771                                         "command, you can define the access level required for most\n"
00772                                         "of %s's functions. (The \002SET FOUNDER\002 and this command\n"
00773                                         "are always restricted to the channel founder.)\n"
00774                                         " \n"
00775                                         "\002LEVELS SET\002 allows the access level for a function or group of\n"
00776                                         "functions to be changed. \002LEVELS DISABLE\002 (or \002DIS\002 for short)\n"
00777                                         "disables an automatic feature or disallows access to a\n"
00778                                         "function by anyone, INCLUDING the founder (although, the founder\n"
00779                                         "can always reenable it).\n"
00780                                         " \n"
00781                                         "\002LEVELS LIST\002 shows the current levels for each function or\n"
00782                                         "group of functions. \002LEVELS RESET\002 resets the levels to the\n"
00783                                         "default levels of a newly-created channel (see\n"
00784                                         "\002HELP ACCESS LEVELS\002).\n"
00785                                         " \n"
00786                                         "For a list of the features and functions whose levels can be\n"
00787                                         "set, see \002HELP LEVELS DESC\002."), source.service->nick.c_str());
00788                 }
00789                 return true;
00790         }
00791 };
00792 
00793 class CSAccess : public Module
00794 {
00795         AccessAccessProvider accessprovider;
00796         CommandCSAccess commandcsaccess;
00797         CommandCSLevels commandcslevels;
00798 
00799  public:
00800         CSAccess(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE),
00801                 accessprovider(this), commandcsaccess(this), commandcslevels(this)
00802         {
00803                 this->SetAuthor("Anope");
00804                 this->SetPermanent(true);
00805 
00806                 Implementation i[] = { I_OnReload, I_OnCreateChan, I_OnGroupCheckPriv };
00807                 ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation));
00808 
00809                 try
00810                 {
00811                         this->OnReload();
00812                 }
00813                 catch (const ConfigException &ex)
00814                 {
00815                         throw ModuleException(ex.GetReason());
00816                 }
00817         }
00818 
00819         void OnReload() anope_override
00820         {
00821                 defaultLevels.clear();
00822                 ConfigReader config;
00823 
00824                 for (int i = 0; i < config.Enumerate("privilege"); ++i)
00825                 {
00826                         const Anope::string &pname = config.ReadValue("privilege", "name", "", i);
00827 
00828                         Privilege *p = PrivilegeManager::FindPrivilege(pname);
00829                         if (p == NULL)
00830                                 continue;
00831 
00832                         const Anope::string &value = config.ReadValue("privilege", "level", "", i);
00833                         if (value.empty())
00834                                 continue;
00835                         else if (value.equals_ci("founder"))
00836                                 defaultLevels[p->name] = ACCESS_FOUNDER;
00837                         else if (value.equals_ci("disabled"))
00838                                 defaultLevels[p->name] = ACCESS_INVALID;
00839                         else
00840                                 defaultLevels[p->name] = config.ReadInteger("privilege", "level", i, false);
00841                 }
00842         }
00843 
00844         void OnCreateChan(ChannelInfo *ci) anope_override
00845         {
00846                 reset_levels(ci);
00847         }
00848 
00849         EventReturn OnGroupCheckPriv(const AccessGroup *group, const Anope::string &priv) anope_override
00850         {
00851                 if (group->ci == NULL)
00852                         return EVENT_CONTINUE;
00853                 /* Special case. Allows a level of < 0 to match anyone, and a level of 0 to match anyone identified. */
00854                 int16_t level = group->ci->GetLevel(priv);
00855                 if (level < 0)
00856                         return EVENT_ALLOW;
00857                 else if (level == 0 && group->nc)
00858                         return EVENT_ALLOW;
00859                 return EVENT_CONTINUE;
00860         }
00861 };
00862 
00863 MODULE_INIT(CSAccess)