/* SOP/AOP/VOP handling for ChanServ.
*
* IRC Services is copyright (c) 1996-2007 Andrew Church.
* E-mail: <achurch@achurch.org>
* 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;
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1