rendered paste body/* ChanServ functions.
*
* IRC Services is copyright (c) 1996-2002 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts copyright (c) 1999-2000 Andrew Kempe and others.
* This program is free but copyrighted software; see the file COPYING for
* details.
*/
/*************************************************************************/
#include "services.h"
#include "pseudo.h"
/*************************************************************************/
/************************** Declaration section **************************/
/*************************************************************************/
#define HASH(chan) (hashtable[(unsigned char)((chan)[1])]<<5 \
| (chan[1] ? hashtable[(unsigned char)((chan)[2])] : 0))
#define HASHSIZE 1024
static ChannelInfo *chanlists[HASHSIZE];
static const char hashtable[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
16,17,18,19,20,21,22,23,24,25,26,27,28,29, 0, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,30,
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,
};
/*************************************************************************/
/* Channel option list. */
typedef struct {
char *name;
int32 flag;
int namestr; /* If -1, will be ignored by cs_flags_to_string() */
int onstr, offstr, syntaxstr;
} ChanOpt;
#define CHANOPT(x) \
{ #x, CI_##x, CHAN_INFO_OPT_##x, \
CHAN_SET_##x##_ON, CHAN_SET_##x##_OFF, CHAN_SET_##x##_SYNTAX }
ChanOpt chanopts[] = {
CHANOPT(KEEPTOPIC),
CHANOPT(TOPICLOCK),
CHANOPT(PRIVATE),
CHANOPT(SECUREOPS),
CHANOPT(LEAVEOPS),
CHANOPT(RESTRICTED),
CHANOPT(SECURE),
CHANOPT(OPNOTICE),
CHANOPT(ENFORCE),
{ "NOEXPIRE", CI_NOEXPIRE, -1, CHAN_SET_NOEXPIRE_ON,
CHAN_SET_NOEXPIRE_OFF, CHAN_SET_NOEXPIRE_SYNTAX },
{ NULL }
};
#undef CHANOPT
/*************************************************************************/
/* Access level control data. */
/* Default levels. All levels MUST be listed here irrespective of IRC
* server type. */
static int def_levels[][2] = {
{ CA_AUTOOP, 5 },
{ CA_AUTOVOICE, 3 },
{ CA_AUTODEOP, -1 },
{ CA_NOJOIN, -2 },
{ CA_INVITE, 5 },
{ CA_AKICK, 10 },
{ CA_SET, ACCLEV_INVALID },
{ CA_CLEAR, ACCLEV_INVALID },
{ CA_UNBAN, 5 },
{ CA_OPDEOP, 5 },
{ CA_ACCESS_LIST, 0 },
{ CA_ACCESS_CHANGE, 10 },
{ CA_MEMO, 10 },
{ CA_VOICE, 3 },
{ CA_AUTOHALFOP, 4 },
{ CA_HALFOP, 4 },
{ CA_AUTOPROTECT, 10 },
{ CA_PROTECT, 10 },
{ -1 }
};
/* Information for LEVELS command. Levels not listed here will not be
* viewable or settable by the user. */
typedef struct {
int what;
char *name;
int desc;
} LevelInfo;
static LevelInfo levelinfo[] = {
{ CA_AUTOOP, "AUTOOP", CHAN_LEVEL_AUTOOP },
{ CA_AUTOVOICE, "AUTOVOICE", CHAN_LEVEL_AUTOVOICE },
#ifdef HAVE_HALFOP
{ CA_AUTODEOP, "AUTODEOP", CHAN_LEVEL_AUTODEOP_HALFOP },
#else
{ CA_AUTODEOP, "AUTODEOP", CHAN_LEVEL_AUTODEOP },
#endif
{ CA_NOJOIN, "NOJOIN", CHAN_LEVEL_NOJOIN },
{ CA_INVITE, "INVITE", CHAN_LEVEL_INVITE },
{ CA_AKICK, "AKICK", CHAN_LEVEL_AKICK },
{ CA_SET, "SET", CHAN_LEVEL_SET },
{ CA_CLEAR, "CLEAR", CHAN_LEVEL_CLEAR },
{ CA_UNBAN, "UNBAN", CHAN_LEVEL_UNBAN },
{ CA_OPDEOP, "OP-DEOP", CHAN_LEVEL_OPDEOP },
{ CA_ACCESS_LIST, "ACC-LIST", CHAN_LEVEL_ACCESS_LIST },
{ CA_ACCESS_CHANGE, "ACC-CHANGE", CHAN_LEVEL_ACCESS_CHANGE },
{ CA_MEMO, "MEMO", CHAN_LEVEL_MEMO },
{ CA_VOICE, "VOICE", CHAN_LEVEL_VOICE },
#ifdef HAVE_HALFOP
{ CA_AUTOHALFOP, "AUTOHALFOP", CHAN_LEVEL_AUTOHALFOP },
{ CA_HALFOP, "HALFOP", CHAN_LEVEL_HALFOP },
#endif
#ifdef HAVE_CHANPROT
{ CA_AUTOPROTECT, "AUTOPROTECT",CHAN_LEVEL_AUTOPROTECT },
{ CA_PROTECT, "PROTECT", CHAN_LEVEL_PROTECT },
#endif
{ -1 }
};
static int levelinfo_maxwidth = 0;
/*************************************************************************/
/* Local functions. */
static void alpha_insert_chan(ChannelInfo *ci);
static ChannelInfo *makechan(const char *chan);
static int delchan(ChannelInfo *ci);
static void count_chan(ChannelInfo *ci);
static void uncount_chan(ChannelInfo *ci);
static void reset_levels(ChannelInfo *ci);
static int is_founder(User *user, ChannelInfo *ci);
static int is_identified(User *user, ChannelInfo *ci);
static int get_access(User *user, ChannelInfo *ci);
static void suspend(ChannelInfo *ci, const char *reason,
const char *who, const time_t expires);
static void unsuspend(ChannelInfo *ci, int set_time);
static void chan_bad_password(User *u, ChannelInfo *ci);
static ChanOpt *chanopt_from_name(const char *optname);
static void do_help(User *u);
static void do_register(User *u);
static void do_identify(User *u);
static void do_drop(User *u);
static void do_set(User *u);
static void do_unset(User *u);
static void do_set_founder(User *u, ChannelInfo *ci, char *param);
static void do_set_successor(User *u, ChannelInfo *ci, char *param);
static void do_set_password(User *u, ChannelInfo *ci, char *param);
static void do_set_desc(User *u, ChannelInfo *ci, char *param);
static void do_set_url(User *u, ChannelInfo *ci, char *param);
static void do_set_email(User *u, ChannelInfo *ci, char *param);
static void do_set_entrymsg(User *u, ChannelInfo *ci, char *param);
static void do_set_topic(User *u, ChannelInfo *ci, char *param);
static void do_set_mlock(User *u, ChannelInfo *ci, char *param);
static void do_set_boolean(User *u, ChannelInfo *ci, ChanOpt *co, char *param);
static void do_access(User *u);
static void do_sop(User *u);
static void do_aop(User *u);
#ifdef HAVE_HALFOP
static void do_hop(User *u);
#endif
static void do_vop(User *u);
static void do_akick(User *u);
static void do_info(User *u);
static void do_list(User *u);
static void do_invite(User *u);
static void do_levels(User *u);
static void do_op(User *u);
static void do_deop(User *u);
static void do_voice(User *u);
static void do_devoice(User *u);
#ifdef HAVE_HALFOP
static void do_halfop(User *u);
static void do_dehalfop(User *u);
#endif
#ifdef HAVE_CHANPROT
static void do_protect(User *u);
static void do_deprotect(User *u);
#endif
static void do_unban(User *u);
static void do_clear(User *u);
static void do_getpass(User *u);
static void do_forbid(User *u);
static void do_suspend(User *u);
static void do_unsuspend(User *u);
static void do_status(User *u);
/*************************************************************************/
/* Command list. */
static Command cmds[] = {
{ "HELP", do_help, NULL, -1, -1,-1,-1,-1 },
{ "REGISTER", do_register, NULL, CHAN_HELP_REGISTER, -1,-1,-1,-1 },
{ "IDENTIFY", do_identify, NULL, CHAN_HELP_IDENTIFY, -1,-1,-1,-1 },
{ "DROP", do_drop, NULL, -1,
CHAN_HELP_DROP, CHAN_SERVADMIN_HELP_DROP,
CHAN_SERVADMIN_HELP_DROP, CHAN_SERVADMIN_HELP_DROP },
{ "SET", do_set, NULL, CHAN_HELP_SET,
-1, CHAN_SERVADMIN_HELP_SET,
CHAN_SERVADMIN_HELP_SET, CHAN_SERVADMIN_HELP_SET },
{ "SET FOUNDER", NULL, NULL, CHAN_HELP_SET_FOUNDER, -1,-1,-1,-1 },
{ "SET SUCCESSOR", NULL, NULL, CHAN_HELP_SET_SUCCESSOR, -1,-1,-1,-1 },
{ "SET PASSWORD", NULL, NULL, CHAN_HELP_SET_PASSWORD, -1,-1,-1,-1 },
{ "SET DESC", NULL, NULL, CHAN_HELP_SET_DESC, -1,-1,-1,-1 },
{ "SET URL", NULL, NULL, CHAN_HELP_SET_URL, -1,-1,-1,-1 },
{ "SET EMAIL", NULL, NULL, CHAN_HELP_SET_EMAIL, -1,-1,-1,-1 },
{ "SET ENTRYMSG", NULL, NULL, CHAN_HELP_SET_ENTRYMSG, -1,-1,-1,-1 },
{ "SET TOPIC", NULL, NULL, CHAN_HELP_SET_TOPIC, -1,-1,-1,-1 },
{ "SET KEEPTOPIC", NULL, NULL, CHAN_HELP_SET_KEEPTOPIC, -1,-1,-1,-1 },
{ "SET TOPICLOCK", NULL, NULL, CHAN_HELP_SET_TOPICLOCK, -1,-1,-1,-1 },
{ "SET MLOCK", NULL, NULL, CHAN_HELP_SET_MLOCK, -1,-1,-1,-1 },
{ "SET PRIVATE", NULL, NULL, CHAN_HELP_SET_PRIVATE, -1,-1,-1,-1 },
{ "SET RESTRICTED", NULL, NULL, CHAN_HELP_SET_RESTRICTED, -1,-1,-1,-1 },
{ "SET SECURE", NULL, NULL, CHAN_HELP_SET_SECURE, -1,-1,-1,-1 },
{ "SET SECUREOPS", NULL, NULL, CHAN_HELP_SET_SECUREOPS, -1,-1,-1,-1 },
{ "SET LEAVEOPS", NULL, NULL, CHAN_HELP_SET_LEAVEOPS, -1,-1,-1,-1 },
{ "SET OPNOTICE", NULL, NULL, CHAN_HELP_SET_OPNOTICE, -1,-1,-1,-1 },
{ "SET ENFORCE", NULL, NULL, CHAN_HELP_SET_ENFORCE, -1,-1,-1,-1 },
{ "SET NOEXPIRE", NULL, NULL, -1, -1,
CHAN_SERVADMIN_HELP_SET_NOEXPIRE,
CHAN_SERVADMIN_HELP_SET_NOEXPIRE,
CHAN_SERVADMIN_HELP_SET_NOEXPIRE },
{ "UNSET", do_unset, NULL, CHAN_HELP_UNSET,
-1, CHAN_SERVADMIN_HELP_UNSET,
CHAN_SERVADMIN_HELP_UNSET, CHAN_SERVADMIN_HELP_UNSET },
{ "SOP", do_sop, NULL, CHAN_HELP_SOP, -1,-1,-1,-1 },
{ "AOP", do_aop, NULL, CHAN_HELP_AOP, -1,-1,-1,-1 },
#ifdef HAVE_HALFOP
{ "HOP", do_hop, NULL, CHAN_HELP_HOP, -1,-1,-1,-1 },
#endif
{ "VOP", do_vop, NULL, CHAN_HELP_VOP, -1,-1,-1,-1 },
{ "ACCESS", do_access, NULL, CHAN_HELP_ACCESS, -1,-1,-1,-1,
(const char *)ACCLEV_SOP,
(const char *)ACCLEV_AOP,
(const char *)ACCLEV_VOP },
{ "ACCESS LEVELS", NULL, NULL, CHAN_HELP_ACCESS_LEVELS, -1,-1,-1,-1,
(const char *)ACCLEV_SOP,
(const char *)ACCLEV_AOP,
(const char *)ACCLEV_VOP },
{ "AKICK", do_akick, NULL, CHAN_HELP_AKICK, -1,-1,-1,-1 },
{ "LEVELS", do_levels, NULL, CHAN_HELP_LEVELS, -1,-1,-1,-1 },
{ "INFO", do_info, NULL, CHAN_HELP_INFO,
-1, CHAN_SERVADMIN_HELP_INFO, CHAN_SERVADMIN_HELP_INFO,
CHAN_SERVADMIN_HELP_INFO },
{ "LIST", do_list, NULL, -1,
CHAN_HELP_LIST, CHAN_SERVADMIN_HELP_LIST,
CHAN_SERVADMIN_HELP_LIST, CHAN_SERVADMIN_HELP_LIST },
{ "OP", do_op, NULL, CHAN_HELP_OP, -1,-1,-1,-1 },
{ "DEOP", do_deop, NULL, CHAN_HELP_DEOP, -1,-1,-1,-1 },
{ "VOICE", do_voice, NULL, CHAN_HELP_VOICE, -1,-1,-1,-1 },
{ "DEVOICE", do_devoice, NULL, CHAN_HELP_DEVOICE, -1,-1,-1,-1 },
#ifdef HAVE_HALFOP
{ "HALFOP", do_halfop, NULL, CHAN_HELP_HALFOP, -1,-1,-1,-1 },
{ "DEHALFOP", do_dehalfop, NULL, CHAN_HELP_DEHALFOP, -1,-1,-1,-1 },
#endif
#ifdef HAVE_CHANPROT
{ "PROTECT", do_protect, NULL, CHAN_HELP_PROTECT, -1,-1,-1,-1 },
{ "DEPROTECT",do_deprotect,NULL, CHAN_HELP_DEPROTECT, -1,-1,-1,-1 },
#endif
{ "INVITE", do_invite, NULL, CHAN_HELP_INVITE, -1,-1,-1,-1 },
{ "UNBAN", do_unban, NULL, CHAN_HELP_UNBAN, -1,-1,-1,-1 },
{ "CLEAR", do_clear, NULL, CHAN_HELP_CLEAR, -1,-1,-1,-1 },
{ "GETPASS", do_getpass, is_services_admin, -1,
-1, CHAN_SERVADMIN_HELP_GETPASS,
CHAN_SERVADMIN_HELP_GETPASS, CHAN_SERVADMIN_HELP_GETPASS },
{ "FORBID", do_forbid, is_services_admin, -1,
-1, CHAN_SERVADMIN_HELP_FORBID,
CHAN_SERVADMIN_HELP_FORBID, CHAN_SERVADMIN_HELP_FORBID },
{ "SUSPEND", do_suspend, is_services_admin, -1,
-1, CHAN_SERVADMIN_HELP_SUSPEND,
CHAN_SERVADMIN_HELP_SUSPEND, CHAN_SERVADMIN_HELP_SUSPEND },
{ "UNSUSPEND",do_unsuspend,is_services_admin, -1,
-1, CHAN_SERVADMIN_HELP_UNSUSPEND,
CHAN_SERVADMIN_HELP_UNSUSPEND, CHAN_SERVADMIN_HELP_UNSUSPEND },
{ "STATUS", do_status, is_services_admin, -1,
-1, CHAN_SERVADMIN_HELP_STATUS,
CHAN_SERVADMIN_HELP_STATUS, CHAN_SERVADMIN_HELP_STATUS },
{ NULL }
};
/*************************************************************************/
/************************ Main ChanServ routines *************************/
/*************************************************************************/
/* ChanServ initialization. */
void cs_init(void)
{
Command *cmd;
cmd = lookup_cmd(cmds, "REGISTER");
if (cmd)
cmd->help_param1 = s_NickServ;
cmd = lookup_cmd(cmds, "SET SECURE");
if (cmd)
cmd->help_param1 = s_NickServ;
cmd = lookup_cmd(cmds, "SET SUCCESSOR");
if (cmd)
cmd->help_param1 = (char *)(long)CSMaxReg;
}
/*************************************************************************/
/* Main ChanServ routine. */
void chanserv(const char *source, char *buf)
{
char *cmd, *s;
User *u = finduser(source);
if (!u) {
log("%s: user record for %s not found", s_ChanServ, source);
notice(s_ChanServ, source,
getstring((NickInfo *)NULL, USER_RECORD_NOT_FOUND));
return;
}
cmd = strtok(buf, " ");
if (!cmd) {
return;
} else if (stricmp(cmd, "\1PING") == 0) {
if (!(s = strtok(NULL, "")))
s = "\1";
notice(s_ChanServ, source, "\1PING %s", s);
} else if (skeleton) {
notice_lang(s_ChanServ, u, SERVICE_OFFLINE, s_ChanServ);
} else {
run_cmd(s_ChanServ, u, cmds, cmd);
}
}
/*************************************************************************/
/* Return the ChannelInfo structure for the given channel, or NULL if the
* channel isn't registered. */
ChannelInfo *cs_findchan(const char *chan)
{
ChannelInfo *ci;
for (ci = chanlists[HASH(chan)]; ci; ci = ci->next) {
if (irc_stricmp(ci->name, chan) == 0)
return ci;
}
return NULL;
}
/*************************************************************************/
/* Iterate over all ChannelInfo structures. Return NULL when called after
* reaching the last channel.
*/
static int iterator_pos = HASHSIZE; /* return NULL initially */
static ChannelInfo *iterator_ptr = NULL;
ChannelInfo *cs_firstchan(void)
{
iterator_pos = -1;
iterator_ptr = NULL;
return cs_nextchan();
}
ChannelInfo *cs_nextchan(void)
{
if (iterator_ptr)
iterator_ptr = iterator_ptr->next;
while (!iterator_ptr && iterator_pos < HASHSIZE) {
iterator_pos++;
if (iterator_pos < HASHSIZE)
iterator_ptr = chanlists[iterator_pos];
}
return iterator_ptr;
}
/*************************************************************************/
/* Return information on memory use. Assumes pointers are valid. */
void get_chanserv_stats(long *nrec, long *memuse)
{
long count = 0, mem = 0;
int i;
ChannelInfo *ci;
for (ci = cs_firstchan(); ci; ci = cs_nextchan()) {
count++;
mem += sizeof(*ci);
if (ci->desc)
mem += strlen(ci->desc)+1;
if (ci->url)
mem += strlen(ci->url)+1;
if (ci->email)
mem += strlen(ci->email)+1;
mem += ci->accesscount * sizeof(ChanAccess);
mem += ci->akickcount * sizeof(AutoKick);
for (i = 0; i < ci->akickcount; i++) {
if (!ci->akick[i].is_nick && ci->akick[i].u.mask)
mem += strlen(ci->akick[i].u.mask)+1;
if (ci->akick[i].reason)
mem += strlen(ci->akick[i].reason)+1;
}
if (ci->mlock_key)
mem += strlen(ci->mlock_key)+1;
if (ci->last_topic)
mem += strlen(ci->last_topic)+1;
if (ci->entry_message)
mem += strlen(ci->entry_message)+1;
if (ci->levels)
mem += sizeof(*ci->levels) * CA_SIZE;
}
*nrec = count;
*memuse = mem;
}
/*************************************************************************/
/************************ Global utility routines ************************/
/*************************************************************************/
#include "cs-loadsave.c"
/*************************************************************************/
/* Check the current modes on a channel; if they conflict with a mode lock,
* fix them. */
void check_modes(const char *chan)
{
Channel *c = findchan(chan);
ChannelInfo *ci;
char newmodes[40]; /* 31 possible modes + extra leeway */
char *newkey = NULL;
int32 newlimit = 0;
char *end = newmodes;
int modes;
int set_limit = 0, set_key = 0;
if (!c || c->bouncy_modes)
return;
if (!NoBouncyModes) {
/* Check for mode bouncing */
if (c->server_modecount >= 3 && c->chanserv_modecount >= 3) {
#if defined(IRC_DALNET) || defined(IRC_UNDERNET)
wallops(NULL, "Warning: unable to set modes on channel %s. "
"Are your servers' U:lines configured correctly?", chan);
#else
wallops(NULL, "Warning: unable to set modes on channel %s. "
"Are your servers configured correctly?", chan);
#endif
log("%s: Bouncy modes on channel %s", s_ChanServ, c->name);
c->bouncy_modes = 1;
return;
}
if (c->chanserv_modetime != time(NULL)) {
c->chanserv_modecount = 0;
c->chanserv_modetime = time(NULL);
}
c->chanserv_modecount++;
}
ci = c->ci;
if (!ci) {
/* Services _always_ knows who should be +r. If a channel tries to be
* +r and is not registered, send mode -r. This will compensate for
* servers that are split when mode -r is initially sent and then try
* to set +r when they rejoin. -TheShadow
*/
if (c->mode & CMODE_REG) {
c->mode &= ~CMODE_REG;
send_cmd(MODE_SENDER(s_ChanServ), "MODE %s -%s", c->name,
mode_flags_to_string(CMODE_REG, MODE_CHANNEL));
}
return;
}
modes = ~c->mode & (ci->mlock_on | CMODE_REG);
end += snprintf(end, sizeof(newmodes)-(end-newmodes)-2,
"+%s", mode_flags_to_string(modes, MODE_CHANNEL));
c->mode |= modes;
if (ci->mlock_limit && ci->mlock_limit != c->limit) {
*end++ = 'l';
newlimit = ci->mlock_limit;
c->limit = newlimit;
set_limit = 1;
}
if (ci->mlock_key) {
if (c->key && strcmp(c->key, ci->mlock_key) != 0) {
/* Send this directly, since send_cmode() will edit it out */
send_cmd(MODE_SENDER(s_ChanServ), "MODE %s -k %s",
c->name, c->key);
free(c->key);
c->key = NULL;
}
if (!c->key) {
*end++ = 'k';
newkey = ci->mlock_key;
c->key = sstrdup(newkey);
set_key = 1;
}
}
if (end[-1] == '+')
end--;
modes = c->mode & ci->mlock_off;
end += snprintf(end, sizeof(newmodes)-(end-newmodes)-1,
"-%s", mode_flags_to_string(modes, MODE_CHANNEL));
c->mode &= ~modes;
if (c->limit && (ci->mlock_off & CMODE_l)) {
*end++ = 'l';
c->limit = 0;
}
if (c->key && (ci->mlock_off & CMODE_k)) {
*end++ = 'k';
newkey = sstrdup(c->key);
free(c->key);
c->key = NULL;
set_key = 1;
}
if (end[-1] == '-')
end--;
if (end == newmodes)
return;
*end = 0;
if (set_limit) {
char newlimit_str[32];
snprintf(newlimit_str, sizeof(newlimit_str), "%d", newlimit);
send_cmode(MODE_SENDER(s_ChanServ), c->name, newmodes, newlimit_str,
newkey ? newkey : "");
} else {
send_cmode(MODE_SENDER(s_ChanServ), c->name, newmodes,
newkey ? newkey : "");
}
if (newkey && !c->key)
free(newkey);
}
/*************************************************************************/
/* Check whether a user should be opped or voiced on a channel, and if so,
* do it. Return the user's new modes on the channel (CUMODE_* flags).
* Updates the channel's last used time if the user is opped. `modes' is
* the user's current mode set. `source' is the source of the message
* which caused the mode change, NULL for a join.
* On Unreal servers, sets +q mode for channel founder or identified users.
*/
int check_chan_user_modes(const char *source, User *user, const char *chan,
int32 modes)
{
ChannelInfo *ci = cs_findchan(chan);
int is_servermode = (!source || strchr(source, '.') != NULL);
/* Don't change modes on unregistered, forbidden, or modeless channels */
if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
return modes;
/* Don't reverse mode changes made by the user him/herself, or by
* Services (because we prevent people from doing improper mode changes
* via Services already, so anything that gets here must be okay). */
if (source && (irc_stricmp(source, user->nick) == 0
|| irc_stricmp(source, MODE_SENDER(s_ChanServ)) == 0
|| irc_stricmp(source, MODE_SENDER(s_OperServ)) == 0))
return modes;
#ifdef IRC_UNREAL
/* Don't change modes for a +I user */
if (user->mode & UMODE_I)
return modes;
#endif
/* Check early for server auto-ops */
if ((modes & CUMODE_o)
&& !(ci->flags & CI_LEAVEOPS)
&& is_servermode
&& time(NULL)-start_time >= CSRestrictDelay
&& !check_access(user, ci, CA_AUTOOP)
) {
notice_lang(s_ChanServ, user, CHAN_IS_REGISTERED, s_ChanServ);
send_cmode(MODE_SENDER(s_ChanServ), chan, "-o", user->nick);
modes &= ~CUMODE_o;
}
/* Mode additions. Only check for join or server mode change, unless
* ENFORCE is set */
if (is_servermode || (ci->flags & CI_ENFORCE)) {
#ifdef HAVE_CHANPROT
if (is_founder(user, ci)) {
ci->last_used = time(NULL);
if (!(modes & CUMODE_q))
send_cmode(MODE_SENDER(s_ChanServ), chan, "+q", user->nick);
modes |= CUMODE_q;
} else if (check_access(user, ci, CA_AUTOPROTECT)) {
ci->last_used = time(NULL);
if (!(modes & CUMODE_a))
send_cmode(MODE_SENDER(s_ChanServ), chan, "+a", user->nick);
modes |= CUMODE_a;
}
#endif
if (check_access(user, ci, CA_AUTOOP)) {
ci->last_used = time(NULL);
if (!(modes & CUMODE_o))
send_cmode(MODE_SENDER(s_ChanServ), chan, "+o", user->nick);
modes |= CUMODE_o;
#ifdef HAVE_HALFOP
} else if (check_access(user, ci, CA_AUTOHALFOP)) {
if (!(modes & CUMODE_h))
send_cmode(MODE_SENDER(s_ChanServ), chan, "+h", user->nick);
modes |= CUMODE_h;
#endif
} else if (check_access(user, ci, CA_AUTOVOICE)) {
if (!(modes & CUMODE_v))
send_cmode(MODE_SENDER(s_ChanServ), chan, "+v", user->nick);
modes |= CUMODE_v;
}
}
/* Don't subtract modes from opers or Services admins */
if (is_oper_u(user) || is_services_admin(user))
return modes;
/* Mode subtractions */
if (check_access(user, ci, CA_AUTODEOP)) {
if (modes & CUMODE_o) {
send_cmode(MODE_SENDER(s_ChanServ), chan, "-o", user->nick);
modes &= ~CUMODE_o;
}
#ifdef HAVE_HALFOP
if (modes & CUMODE_h) {
send_cmode(MODE_SENDER(s_ChanServ), chan, "-h", user->nick);
modes &= ~CUMODE_h;
}
#endif
#ifdef HAVE_CHANPROT
if (modes & CUMODE_a) {
send_cmode(MODE_SENDER(s_ChanServ), chan, "-a", user->nick);
modes &= ~CUMODE_a;
}
if (modes & CUMODE_q) {
send_cmode(MODE_SENDER(s_ChanServ), chan, "-q", user->nick);
modes &= ~CUMODE_q;
}
#endif
}
return modes;
}
/*************************************************************************/
/* Tiny helper routine to get ChanServ out of a channel after it went in. */
static void timeout_leave(Timeout *to)
{
char *chan = to->data;
send_cmd(s_ChanServ, "PART %s", chan);
free(to->data);
}
/* Check whether a user is permitted to be on a channel. If so, return 0;
* else, kickban the user with an appropriate message (could be either
* AKICK or restricted access) and return 1. Note that this is called
* _before_ the user is added to internal channel lists (so do_kick() is
* not called).
*/
int check_kick(User *user, const char *chan)
{
Channel *c = findchan(chan);
ChannelInfo *ci = cs_findchan(chan);
AutoKick *akick;
int i;
NickInfo *ni;
char *av[3], *mask, *s;
const char *reason;
Timeout *t;
int stay;
if (!ci)
return 0;
if (is_services_admin(user))
return 0;
#ifdef IRC_UNREAL
/* Don't let plain opers into +A (admin only) channels */
if ((ci->mlock_on & CMODE_A) && !(user->mode & (UMODE_A|UMODE_N|UMODE_T))){
mask = create_mask(user, 1);
reason = getstring(user->ni, CHAN_NOT_ALLOWED_TO_JOIN);
goto kick;
}
/* Don't let hiding users into no-hiding channels */
if ((ci->mlock_on & CMODE_H) && (user->mode & UMODE_I)){
mask = create_mask(user, 1);
reason = getstring(user->ni, CHAN_NOT_ALLOWED_TO_JOIN);
goto kick;
}
#endif /* IRC_UNREAL */
if (is_oper_u(user))
return 0;
if ((ci->flags & CI_VERBOTEN) || ci->suspendinfo) {
mask = sstrdup("*!*@*");
reason = getstring(user->ni, CHAN_MAY_NOT_BE_USED);
goto kick;
}
if (ci->mlock_on & CMODE_OPERONLY) {
/* We already know they're not an oper, so kick them off */
mask = create_mask(user, 1);
reason = getstring(user->ni, CHAN_NOT_ALLOWED_TO_JOIN);
goto kick;
}
if (nick_recognized(user))
ni = user->ni;
else
ni = NULL;
for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
if (!akick->in_use)
continue;
if ((akick->is_nick && getlink(akick->u.ni) == ni)
|| (!akick->is_nick && match_usermask(akick->u.mask, user))
) {
if (debug >= 2) {
log("debug: %s matched akick %s", user->nick,
akick->is_nick ? akick->u.ni->nick : akick->u.mask);
}
mask = akick->is_nick ? create_mask(user, 1)
: sstrdup(akick->u.mask);
reason = akick->reason ? akick->reason : CSAutokickReason;
goto kick;
}
}
if (time(NULL)-start_time >= CSRestrictDelay
&& check_access(user, ci, CA_NOJOIN)) {
mask = create_mask(user, 1);
reason = getstring(user->ni, CHAN_NOT_ALLOWED_TO_JOIN);
goto kick;
}
return 0;
kick:
if (debug) {
log("debug: channel: AutoKicking %s!%s@%s",
user->nick, user->username, user->host);
}
/* Remember that the user has not been added to our channel user list
* yet, so we check whether the channel does not exist */
stay = (c == NULL);
av[0] = (char *)chan;
if (stay) {
send_cmd(s_ChanServ, "JOIN %s", chan);
t = add_timeout(CSInhabit, timeout_leave, 0);
t->data = sstrdup(chan);
}
/* Make sure the mask has an ! in it */
if (!(s = strchr(mask, '!')) || s > strchr(mask, '@')) {
int len = strlen(mask);
mask = srealloc(mask, len+3);
memmove(mask+2, mask, len+1);
mask[0] = '*';
mask[1] = '!';
}
/* Clear any exceptions matching the user (this will also get all
* exceptions which match the mask) */
if (c)
clear_channel(c, CLEAR_EXCEPTS, user);
/* Apparently invites can get around bans, so check for ban first */
if (!chan_has_ban(chan, mask)) {
av[1] = "+b";
av[2] = mask;
send_cmode(MODE_SENDER(s_ChanServ), chan, "+b", mask);
do_cmode(MODE_SENDER(s_ChanServ), 3, av);
}
free(mask);
send_cmd(MODE_SENDER(s_ChanServ), "KICK %s %s :%s", chan, user->nick,
reason);
return 1;
}
/*************************************************************************/
/* Record the current channel topic in the ChannelInfo structure. */
void record_topic(Channel *c)
{
ChannelInfo *ci = c->ci;
if (readonly || !ci)
return;
if (ci->last_topic)
free(ci->last_topic);
if (c->topic)
ci->last_topic = sstrdup(c->topic);
else
ci->last_topic = NULL;
strscpy(ci->last_topic_setter, c->topic_setter, NICKMAX);
ci->last_topic_time = c->topic_time;
}
/*************************************************************************/
/* Restore the topic in a channel when it's created, if we should. */
void restore_topic(Channel *c)
{
ChannelInfo *ci = c->ci;
if (!ci || !(ci->flags & CI_KEEPTOPIC))
return;
set_topic(c, ci->last_topic,
*ci->last_topic_setter ? ci->last_topic_setter : s_ChanServ,
ci->last_topic_time);
}
/*************************************************************************/
/* See if the topic is locked on the given channel, and return 1 (and fix
* the topic) if so, 0 if not. */
int check_topiclock(const char *chan)
{
Channel *c = findchan(chan);
ChannelInfo *ci;
if (!c || !(ci = c->ci) || !(ci->flags & CI_TOPICLOCK))
return 0;
set_topic(c, ci->last_topic,
*ci->last_topic_setter ? ci->last_topic_setter : s_ChanServ,
ci->last_topic_time);
return 1;
}
/*************************************************************************/
/* Remove all channels and clear all suspensions which have expired. */
void expire_chans()
{
ChannelInfo *ci, *next;
time_t now = time(NULL);
Channel *c;
if (!CSExpire)
return;
for (ci = cs_firstchan(); ci; ci = next) {
next = cs_nextchan();
if (now >= ci->last_used + CSExpire
&& !(ci->flags & (CI_VERBOTEN | CI_NOEXPIRE))
&& !ci->suspendinfo
) {
log("%s: Expiring channel %s", s_ChanServ, ci->name);
if (CMODE_REG && (c = findchan(ci->name))) {
c->mode &= ~CMODE_REG;
/* Send this out immediately, no send_cmode() delay */
send_cmd(s_ChanServ, "MODE %s -%s", ci->name,
mode_flags_to_string(CMODE_REG, MODE_CHANNEL));
}
delchan(ci);
} else if (ci->suspendinfo && ci->suspendinfo->expires
&& now >= ci->suspendinfo->expires) {
log("%s: Expiring suspension for %s", s_ChanServ, ci->name);
unsuspend(ci, 1);
}
}
}
/*************************************************************************/
/* Remove a (deleted or expired) nickname from all channel lists. */
void cs_remove_nick(const NickInfo *ni)
{
int i;
ChannelInfo *ci, *next;
ChanAccess *ca;
AutoKick *akick;
for (ci = cs_firstchan(); ci; ci = next) {
next = cs_nextchan();
if (ci->founder == ni) {
int was_suspended = (ci->suspendinfo != NULL);
char name_save[CHANMAX];
if (was_suspended)
strscpy(name_save, ci->name, CHANMAX);
uncount_chan(ci); /* Make sure it disappears from founderchans */
if (ci->successor) {
NickInfo *ni2 = ci->successor;
if (check_channel_limit(ni2) < 0) {
log("%s: Transferring foundership of %s from deleted "
"nick %s to successor %s",
s_ChanServ, ci->name, ni->nick, ni2->nick);
ci->founder = ni2;
ci->successor = NULL;
count_chan(ci);
} else {
log("%s: Successor (%s) of %s owns too many channels, "
"deleting channel",
s_ChanServ, ni2->nick, ci->name);
goto delete;
}
} else {
log("%s: Deleting channel %s owned by deleted nick %s",
s_ChanServ, ci->name, ni->nick);
delete:
delchan(ci);
if (was_suspended) {
/* Channel was suspended, so make it forbidden */
log("%s: Channel %s was suspended, forbidding it",
s_ChanServ, name_save);
ci = makechan(name_save);
ci->flags |= CI_VERBOTEN;
}
continue;
}
}
if (ci->successor == ni)
ci->successor = NULL;
for (ca = ci->access, i = ci->accesscount; i > 0; ca++, i--) {
if (ca->in_use && ca->ni == ni) {
ca->in_use = 0;
ca->ni = NULL;
}
}
for (akick = ci->akick, i = ci->akickcount; i > 0; akick++, i--) {
if (akick->is_nick && akick->u.ni == ni) {
akick->in_use = akick->is_nick = 0;
akick->u.ni = NULL;
if (akick->reason) {
free(akick->reason);
akick->reason = NULL;
}
}
}
}
}
/*************************************************************************/
/* Return 1 if the user's access level on the given channel falls into the
* given category, 0 otherwise. Note that this may seem slightly confusing
* in some cases: for example, check_access(..., CA_NOJOIN) returns true if
* the user does _not_ have access to the channel (i.e. matches the NOJOIN
* criterion). */
int check_access(User *user, ChannelInfo *ci, int what)
{
int level = get_access(user, ci);
int limit = ci->levels[what];
if (level == ACCLEV_FOUNDER)
return (what==CA_AUTODEOP || what==CA_NOJOIN) ? 0 : 1;
/* Hacks to make flags work */
if (what == CA_AUTODEOP && (ci->flags & CI_SECUREOPS) && level == 0)
return 1;
if (limit == ACCLEV_INVALID)
return 0;
if (what == CA_AUTODEOP || what == CA_NOJOIN)
return level <= ci->levels[what];
else
return level >= ci->levels[what];
}
/*************************************************************************/
/* Check the nick's number of registered channels against its limit, and
* return -1 if below the limit, 0 if at it exactly, and 1 if over it.
*/
int check_channel_limit(NickInfo *ni)
{
register uint16 max, count;
ni = getlink(ni);
max = ni->channelmax;
if (!max)
max = MAX_CHANNELCOUNT;
count = ni->channelcount;
return count<max ? -1 : count==max ? 0 : 1;
}
/*************************************************************************/
/* Return a string listing the options (those given in chanopts[]) set on
* the given channel. Uses the given NickInfo for language information.
* The returned string is stored in a static buffer which will be
* overwritten on the next call.
*/
char *chanopts_to_string(ChannelInfo *ci, NickInfo *ni)
{
static char buf[BUFSIZE];
char *end = buf;
const char *commastr = getstring(ni, COMMA_SPACE);
const char *s;
int need_comma = 0;
int i;
for (i = 0; chanopts[i].name && end-buf < sizeof(buf)-1; i++) {
if (ni && chanopts[i].namestr < 0)
continue;
if (ci->flags & chanopts[i].flag && chanopts[i].namestr >= 0) {
s = getstring(ni, chanopts[i].namestr);
if (need_comma)
end += snprintf(end, sizeof(buf)-(end-buf), "%s", commastr);
end += snprintf(end, sizeof(buf)-(end-buf), "%s", s);
need_comma = 1;
}
}
return buf;
}
/*************************************************************************/
/*********************** ChanServ private routines ***********************/
/*************************************************************************/
/* Insert a channel alphabetically into the database. */
static void alpha_insert_chan(ChannelInfo *ci)
{
ChannelInfo *ptr, *prev;
char *chan = ci->name;
int hash = HASH(chan);
for (prev = NULL, ptr = chanlists[hash];
ptr != NULL && irc_stricmp(ptr->name, chan) < 0;
prev = ptr, ptr = ptr->next)
;
ci->prev = prev;
ci->next = ptr;
if (!prev)
chanlists[hash] = ci;
else
prev->next = ci;
if (ptr)
ptr->prev = ci;
}
/*************************************************************************/
/* Add a channel to the database. Returns a pointer to the new ChannelInfo
* structure if the channel was successfully registered, NULL otherwise.
* Assumes channel does not already exist. */
static ChannelInfo *makechan(const char *chan)
{
ChannelInfo *ci;
ci = scalloc(sizeof(ChannelInfo), 1);
strscpy(ci->name, chan, CHANMAX);
ci->time_registered = time(NULL);
reset_levels(ci);
alpha_insert_chan(ci);
return ci;
}
/*************************************************************************/
/* Remove a channel from the ChanServ database. Return 1 on success, 0
* otherwise. */
static int delchan(ChannelInfo *ci)
{
int i;
User *u;
/* Remove channel from founder's owned-channel count */
uncount_chan(ci);
/* Clear channel from users' identified-channel lists */
for (u = firstuser(); u; u = nextuser()) {
struct u_chaninfolist *uc, *next;
for (uc = u->founder_chans; uc; uc = next) {
next = uc->next;
if (uc->chan == ci) {
if (uc->next)
uc->next->prev = uc->prev;
if (uc->prev)
uc->prev->next = uc->next;
else
u->founder_chans = uc->next;
free(uc);
}
}
}
/* Now actually free channel data */
if (ci->c)
ci->c->ci = NULL;
if (ci->next)
ci->next->prev = ci->prev;
if (ci->prev)
ci->prev->next = ci->next;
else
chanlists[HASH(ci->name)] = ci->next;
if (ci->desc)
free(ci->desc);
if (ci->mlock_key)
free(ci->mlock_key);
if (ci->last_topic)
free(ci->last_topic);
if (ci->suspendinfo)
unsuspend(ci, 0);
if (ci->access)
free(ci->access);
for (i = 0; i < ci->akickcount; i++) {
if (!ci->akick[i].is_nick && ci->akick[i].u.mask)
free(ci->akick[i].u.mask);
if (ci->akick[i].reason)
free(ci->akick[i].reason);
}
if (ci->akick)
free(ci->akick);
if (ci->levels)
free(ci->levels);
if (ci->memos.memos) {
for (i = 0; i < ci->memos.memocount; i++) {
if (ci->memos.memos[i].text)
free(ci->memos.memos[i].text);
}
free(ci->memos.memos);
}
free(ci);
return 1;
}
/*************************************************************************/
/* Mark the given channel as owned by its founder. This updates the
* founder's list of owned channels (ni->founderchans) and the count of
* owned channels for the founder and all linked nicks.
*/
static void count_chan(ChannelInfo *ci)
{
NickInfo *ni = ci->founder;
if (!ni)
return;
ni->foundercount++;
ni->founderchans = srealloc(ni->founderchans,
sizeof(*ni->founderchans) * ni->foundercount);
ni->founderchans[ni->foundercount-1] = ci;
while (ni) {
/* Be paranoid--this could overflow in extreme cases, though we
* check for that elsewhere as well. */
if (ni->channelcount+1 > ni->channelcount)
ni->channelcount++;
ni = ni->link;
}
}
/*************************************************************************/
/* Mark the given channel as no longer owned by its founder. */
static void uncount_chan(ChannelInfo *ci)
{
NickInfo *ni = ci->founder;
int i;
if (!ni)
return;
for (i = 0; i < ni->foundercount; i++) {
if (ni->founderchans[i] == ci) {
ni->foundercount--;
if (i < ni->foundercount)
memmove(&ni->founderchans[i], &ni->founderchans[i+1],
sizeof(*ni->founderchans) * (ni->foundercount-i));
ni->founderchans = srealloc(ni->founderchans,
sizeof(*ni->founderchans) * ni->foundercount);
break;
}
}
while (ni) {
if (ni->channelcount > 0)
ni->channelcount--;
ni = ni->link;
}
}
/*************************************************************************/
/* Reset channel access level values to their default state. */
static void reset_levels(ChannelInfo *ci)
{
int i;
if (ci->levels)
free(ci->levels);
ci->levels = scalloc(CA_SIZE, sizeof(*ci->levels));
for (i = 0; def_levels[i][0] >= 0; i++)
ci->levels[def_levels[i][0]] = def_levels[i][1];
}
/*************************************************************************/
/* Does the given user have founder access to the channel? */
static int is_founder(User *user, ChannelInfo *ci)
{
if ((ci->flags & CI_VERBOTEN) || ci->suspendinfo)
return 0;
if (user->ni == getlink(ci->founder)) {
if ((nick_identified(user) ||
(nick_recognized(user) && !(ci->flags & CI_SECURE))))
return 1;
}
if (is_identified(user, ci))
return 1;
return 0;
}
/*************************************************************************/
/* Has the given user password-identified as founder for the channel? */
static int is_identified(User *user, ChannelInfo *ci)
{
struct u_chaninfolist *c;
for (c = user->founder_chans; c; c = c->next) {
if (c->chan == ci)
return 1;
}
return 0;
}
/*************************************************************************/
/* Return the access level the given user has on the channel. If the
* channel doesn't exist, the user isn't on the access list, or the channel
* is CS_SECURE and the user hasn't IDENTIFY'd with NickServ, return 0. */
static int get_access(User *user, ChannelInfo *ci)
{
NickInfo *ni = user->ni;
ChanAccess *access;
int i;
if (!ci || !ni || (ci->flags & CI_VERBOTEN) || ci->suspendinfo)
return 0;
if (is_founder(user, ci))
return ACCLEV_FOUNDER;
if (nick_identified(user)
|| (nick_recognized(user) && !(ci->flags & CI_SECURE))
) {
for (access = ci->access, i = 0; i < ci->accesscount; access++, i++) {
if (access->in_use && getlink(access->ni) == ni)
return access->level;
}
}
return 0;
}
/*************************************************************************/
/* Create a new SuspendInfo structure and associate it with the given
* channel. */
static void suspend(ChannelInfo *ci, const char *reason,
const char *who, const time_t expires)
{
SuspendInfo *si;
si = scalloc(sizeof(*si), 1);
strscpy(si->who, who, NICKMAX);
si->reason = sstrdup(reason);
si->suspended = time(NULL);
si->expires = expires;
ci->suspendinfo = si;
}
/*************************************************************************/
/* Delete the suspension data for the given channel. We also alter the
* last_seen value to ensure that it does not expire within the next
* CSSuspendGrace seconds, giving its users a chance to reclaim it
* (but only if set_time is non-zero).
*/
static void unsuspend(ChannelInfo *ci, int set_time)
{
time_t now = time(NULL);
if (!ci->suspendinfo) {
log("%s: unsuspend() called on non-suspended channel %s",
s_ChanServ, ci->name);
return;
}
if (ci->suspendinfo->reason)
free(ci->suspendinfo->reason);
free(ci->suspendinfo);
ci->suspendinfo = NULL;
if (set_time && CSExpire && CSSuspendGrace
&& (now - ci->last_used >= CSExpire - CSSuspendGrace)
) {
ci->last_used = now - CSExpire + CSSuspendGrace;
log("%s: unsuspend: Altering last_used time for %s to %ld.",
s_ChanServ, ci->name, ci->last_used);
}
}
/*************************************************************************/
/* Register a bad password attempt for a channel. */
static void chan_bad_password(User *u, ChannelInfo *ci)
{
bad_password(s_ChanServ, u, ci->name);
ci->bad_passwords++;
if (BadPassWarning && ci->bad_passwords == BadPassWarning) {
wallops(s_ChanServ, "\2Warning:\2 Repeated bad password attempts"
" for channel %s", ci->name);
}
if (BadPassSuspend && ci->bad_passwords == BadPassSuspend) {
time_t expire = 0;
if (CSSuspendExpire)
expire = time(NULL) + CSSuspendExpire;
suspend(ci, "Too many bad passwords (automatic suspend)",
s_ChanServ, expire);
log("%s: Automatic suspend for %s (too many bad passwords)",
s_ChanServ, ci->name);
/* Clear bad password count for when channel is unsuspended */
ci->bad_passwords = 0;
}
}
/*************************************************************************/
/* Return the ChanOpt structure for the given option name. If not found,
* return NULL.
*/
static ChanOpt *chanopt_from_name(const char *optname)
{
int i;
for (i = 0; chanopts[i].name; i++) {
if (stricmp(chanopts[i].name, optname) == 0)
return &chanopts[i];
}
return NULL;
}
/*************************************************************************/
/*********************** ChanServ command routines ***********************/
/*************************************************************************/
static void do_help(User *u)
{
char *cmd = strtok(NULL, "");
if (!cmd) {
notice_help(s_ChanServ, u, CHAN_HELP);
#if defined(HAVE_HALFOP) && defined(HAVE_CHANPROT)
notice_help(s_ChanServ, u, CHAN_HELP_OTHERS_HALFOP_CHANPROT);
#elif defined(HAVE_HALFOP)
notice_help(s_ChanServ, u, CHAN_HELP_OTHERS_HALFOP);
#elif defined(HAVE_CHANPROT)
notice_help(s_ChanServ, u, CHAN_HELP_OTHERS_CHANPROT);
#else
notice_help(s_ChanServ, u, CHAN_HELP_OTHERS);
#endif
if (CSExpire >= 86400)
notice_help(s_ChanServ, u, CHAN_HELP_EXPIRES, CSExpire/86400);
if (is_services_oper(u))
notice_help(s_ChanServ, u, CHAN_SERVADMIN_HELP);
} else if (stricmp(cmd, "LEVELS DESC") == 0) {
int i;
notice_help(s_ChanServ, u, CHAN_HELP_LEVELS_DESC);
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++) {
notice_help(s_ChanServ, u, CHAN_HELP_LEVELS_DESC_FORMAT,
levelinfo_maxwidth, levelinfo[i].name,
getstring(u->ni, levelinfo[i].desc));
}
} else if (stricmp(cmd, "CLEAR") == 0) {
notice_help(s_ChanServ, u, CHAN_HELP_CLEAR);
#ifdef HAVE_BANEXCEPT
notice_help(s_ChanServ, u, CHAN_HELP_CLEAR_EXCEPTIONS);
#endif
notice_help(s_ChanServ, u, CHAN_HELP_CLEAR_MID);
#ifdef HAVE_HALFOP
notice_help(s_ChanServ, u, CHAN_HELP_CLEAR_HALFOPS);
#endif
notice_help(s_ChanServ, u, CHAN_HELP_CLEAR_END);
} else if (stricmp(cmd, "SOP") == 0) {
notice_help(s_ChanServ, u, CHAN_HELP_SOP);
#ifdef HAVE_CHANPROT
notice_help(s_ChanServ, u, CHAN_HELP_SOP_PROT);
#else
notice_help(s_ChanServ, u, CHAN_HELP_SOP_NOPROT);
#endif
notice_help(s_ChanServ, u, CHAN_HELP_SOP_END);
} else {
help_cmd(s_ChanServ, u, cmds, cmd);
}
}
/*************************************************************************/
static void do_register(User *u)
{
char *chan = strtok(NULL, " ");
char *pass = strtok(NULL, " ");
char *desc = strtok(NULL, "");
NickInfo *ni = u->ni;
Channel *c;
ChannelInfo *ci;
struct u_chaninfolist *uc;
#ifdef USE_ENCRYPTION
char founderpass[PASSMAX+1];
#endif
if (readonly) {
notice_lang(s_ChanServ, u, CHAN_REGISTER_DISABLED);
return;
}
if (!desc) {
syntax_error(s_ChanServ, u, "REGISTER", CHAN_REGISTER_SYNTAX);
} else if (*chan == '&') {
notice_lang(s_ChanServ, u, CHAN_REGISTER_NOT_LOCAL);
} else if (!ni) {
notice_lang(s_ChanServ, u, CHAN_MUST_REGISTER_NICK, s_NickServ);
} else if (!nick_recognized(u)) {
notice_lang(s_ChanServ, u, CHAN_MUST_IDENTIFY_NICK,
s_NickServ, s_NickServ);
} else if ((ci = cs_findchan(chan)) != NULL) {
if (ci->flags & CI_VERBOTEN) {
log("%s: Attempt to register FORBIDden channel %s by %s!%s@%s",
s_ChanServ, ci->name, u->nick, u->username, u->host);
notice_lang(s_ChanServ, u, CHAN_MAY_NOT_BE_REGISTERED, chan);
} else if (ci->suspendinfo) {
log("%s: Attempt to register SUSPENDed channel %s by %s!%s@%s",
s_ChanServ, ci->name, u->nick, u->username, u->host);
notice_lang(s_ChanServ, u, CHAN_ALREADY_REGISTERED, chan);
} else {
notice_lang(s_ChanServ, u, CHAN_ALREADY_REGISTERED, chan);
}
} else if (!is_chanop(u->nick, chan)) {
notice_lang(s_ChanServ, u, CHAN_MUST_BE_CHANOP);
} else if (check_channel_limit(ni) >= 0) {
int max = ni->channelmax ? ni->channelmax : MAX_CHANNELCOUNT;
notice_lang(s_ChanServ, u,
ni->channelcount > max ? CHAN_EXCEEDED_CHANNEL_LIMIT
: CHAN_REACHED_CHANNEL_LIMIT, max);
} else if (!(c = findchan(chan))) {
log("%s: Channel %s not found for REGISTER", s_ChanServ, chan);
notice_lang(s_ChanServ, u, CHAN_REGISTRATION_FAILED);
} else if (!(ci = makechan(chan))) {
log("%s: makechan() failed for REGISTER %s", s_ChanServ, chan);
notice_lang(s_ChanServ, u, CHAN_REGISTRATION_FAILED);
#ifdef USE_ENCRYPTION
} else if (strscpy(founderpass, pass, PASSMAX+1),
encrypt_in_place(founderpass, PASSMAX) < 0) {
log("%s: Couldn't encrypt password for %s (REGISTER)",
s_ChanServ, chan);
notice_lang(s_ChanServ, u, CHAN_REGISTRATION_FAILED);
delchan(ci);
#endif
} else {
c->ci = ci;
ci->c = c;
ci->flags = CI_KEEPTOPIC | CI_SECURE;
ci->mlock_on = CMODE_n | CMODE_t;
ci->memos.memomax = MSMaxMemos;
ci->last_used = ci->time_registered;
ci->founder = u->real_ni;
#ifdef USE_ENCRYPTION
if (strlen(pass) > PASSMAX)
notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, PASSMAX);
memset(pass, 0, strlen(pass));
memcpy(ci->founderpass, founderpass, PASSMAX);
ci->flags |= CI_ENCRYPTEDPW;
#else
if (strlen(pass) > PASSMAX-1) /* -1 for null byte */
notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, PASSMAX-1);
strscpy(ci->founderpass, pass, PASSMAX);
#endif
ci->desc = sstrdup(desc);
if (c->topic) {
ci->last_topic = sstrdup(c->topic);
strscpy(ci->last_topic_setter, c->topic_setter, NICKMAX);
ci->last_topic_time = c->topic_time;
}
count_chan(ci);
log("%s: Channel %s registered by %s!%s@%s", s_ChanServ, chan,
u->nick, u->username, u->host);
notice_lang(s_ChanServ, u, CHAN_REGISTERED, chan, u->nick);
#ifndef USE_ENCRYPTION
notice_lang(s_ChanServ, u, CHAN_PASSWORD_IS, ci->founderpass);
#endif
uc = smalloc(sizeof(*uc));
uc->next = u->founder_chans;
uc->prev = NULL;
if (u->founder_chans)
u->founder_chans->prev = uc;
u->founder_chans = uc;
uc->chan = ci;
/* Implement new mode lock */
check_modes(ci->name);
}
}
/*************************************************************************/
static void do_identify(User *u)
{
char *chan = strtok(NULL, " ");
char *pass = strtok(NULL, " ");
ChannelInfo *ci;
struct u_chaninfolist *uc;
if (!pass) {
syntax_error(s_ChanServ, u, "IDENTIFY", CHAN_IDENTIFY_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->suspendinfo) {
notice_lang(s_ChanServ, u, CHAN_X_SUSPENDED, chan);
} else {
int res = check_password(pass, ci->founderpass);
if (res == 1) {
ci->bad_passwords = 0;
ci->last_used = time(NULL);
if (!is_identified(u, ci)) {
uc = smalloc(sizeof(*uc));
uc->next = u->founder_chans;
uc->prev = NULL;
if (u->founder_chans)
u->founder_chans->prev = uc;
u->founder_chans = uc;
uc->chan = ci;
log("%s: %s!%s@%s identified for %s", s_ChanServ,
u->nick, u->username, u->host, ci->name);
}
notice_lang(s_ChanServ, u, CHAN_IDENTIFY_SUCCEEDED, chan);
} else if (res < 0) {
log("%s: check_password failed for %s", s_ChanServ, ci->name);
notice_lang(s_ChanServ, u, CHAN_IDENTIFY_FAILED);
} else {
log("%s: Failed IDENTIFY for %s by %s!%s@%s",
s_ChanServ, ci->name, u->nick, u->username, u->host);
chan_bad_password(u, ci);
}
}
}
/*************************************************************************/
static void do_drop(User *u)
{
char *chan = strtok(NULL, " ");
ChannelInfo *ci;
int is_servadmin = is_services_admin(u);
Channel *c;
if (readonly && !is_servadmin) {
notice_lang(s_ChanServ, u, CHAN_DROP_DISABLED);
return;
}
if (!chan) {
syntax_error(s_ChanServ, u, "DROP", CHAN_DROP_SYNTAX);
} else if (!(ci = cs_findchan(chan))) {
notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
} else if (!is_servadmin && (ci->flags & CI_VERBOTEN)) {
notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
} else if (!is_servadmin && !is_identified(u, ci)) {
notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED, s_ChanServ, chan);
} else {
if (readonly) /* in this case we know they're a Services admin */
notice_lang(s_ChanServ, u, READ_ONLY_MODE);
log("%s: Channel %s dropped by %s!%s@%s", s_ChanServ, ci->name,
u->nick, u->username, u->host);
delchan(ci);
if (CMODE_REG && (c = findchan(chan))) {
c->mode &= ~CMODE_REG;
send_cmd(s_ChanServ, "MODE %s -%s", chan,
mode_flags_to_string(CMODE_REG, MODE_CHANNEL));
}
notice_lang(s_ChanServ, u, CHAN_DROPPED, chan);
}
}
/*************************************************************************/
/* Main SET routine. Calls other routines as follows:
* do_set_command(User *command_sender, ChannelInfo *ci, char *param);
* The parameter passed is the first space-delimited parameter after the
* option name, except in the case of DESC, TOPIC, and ENTRYMSG, in which
* it is the entire remainder of the line. Additional parameters beyond
* the first passed in the function call can be retrieved using
* strtok(NULL, toks).
*
* do_set_boolean, the default handler, is an exception to this in that it
* also takes the ChanOpt structure for the selected option as a parameter.
*/
static void do_set(User *u)
{
char *chan = strtok(NULL, " ");
char *cmd = strtok(NULL, " ");
char *param;
ChannelInfo *ci;
int is_servadmin = is_services_admin(u);
if (readonly) {
notice_lang(s_ChanServ, u, CHAN_SET_DISABLED);
return;
}
if (cmd) {
if (stricmp(cmd, "DESC") == 0 || stricmp(cmd, "TOPIC") == 0
|| stricmp(cmd, "ENTRYMSG") == 0)
param = strtok(NULL, "");
else
param = strtok(NULL, " ");
} else {
param = NULL;
}
if (!param) {
syntax_error(s_ChanServ, u, "SET", CHAN_SET_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 (!is_servadmin && !check_access(u, ci, CA_SET)) {
notice_lang(s_ChanServ, u, ACCESS_DENIED);
} else if (stricmp(cmd, "FOUNDER") == 0) {
if (!is_servadmin && !is_founder(u, ci)) {
notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED,s_ChanServ,chan);
} else {
do_set_founder(u, ci, param);
}
} else if (stricmp(cmd, "SUCCESSOR") == 0) {
if (!is_servadmin && !is_founder(u, ci)) {
notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED,s_ChanServ,chan);
} else {
do_set_successor(u, ci, param);
}
} else if (stricmp(cmd, "PASSWORD") == 0) {
if (!is_servadmin && !is_founder(u, ci)) {
notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED,s_ChanServ,chan);
} else {
do_set_password(u, ci, param);
}
} else if (stricmp(cmd, "DESC") == 0) {
do_set_desc(u, ci, param);
} else if (stricmp(cmd, "URL") == 0) {
do_set_url(u, ci, param);
} else if (stricmp(cmd, "EMAIL") == 0) {
do_set_email(u, ci, param);
} else if (stricmp(cmd, "ENTRYMSG") == 0) {
do_set_entrymsg(u, ci, param);
} else if (stricmp(cmd, "TOPIC") == 0) {
do_set_topic(u, ci, param);
} else if (stricmp(cmd, "MLOCK") == 0) {
do_set_mlock(u, ci, param);
} else {
ChanOpt *co = chanopt_from_name(cmd);
if (co && co->flag == CI_NOEXPIRE && !is_servadmin)
co = NULL;
if (co) {
do_set_boolean(u, ci, co, param);
} else {
notice_lang(s_ChanServ, u, CHAN_SET_UNKNOWN_OPTION, strupper(cmd));
notice_lang(s_ChanServ, u, MORE_INFO, s_ChanServ, "SET");
}
}
}
/*************************************************************************/
static void do_unset(User *u)
{
char *chan = strtok(NULL, " ");
char *cmd = strtok(NULL, " ");
ChannelInfo *ci;
int is_servadmin = is_services_admin(u);
if (readonly) {
notice_lang(s_ChanServ, u, CHAN_SET_DISABLED);
return;
}
if (!cmd) {
syntax_error(s_ChanServ, u, "UNSET", CHAN_UNSET_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 (!is_servadmin && !check_access(u, ci, CA_SET)) {
notice_lang(s_ChanServ, u, ACCESS_DENIED);
} else if (stricmp(cmd, "SUCCESSOR") == 0) {
if (!is_servadmin && !is_founder(u, ci)) {
notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED,s_ChanServ,chan);
} else {
do_set_successor(u, ci, NULL);
}
} else if (stricmp(cmd, "URL") == 0) {
do_set_url(u, ci, NULL);
} else if (stricmp(cmd, "EMAIL") == 0) {
do_set_email(u, ci, NULL);
} else if (stricmp(cmd, "ENTRYMSG") == 0) {
do_set_entrymsg(u, ci, NULL);
} else {
syntax_error(s_ChanServ, u, "UNSET", CHAN_UNSET_SYNTAX);
}
}
/*************************************************************************/
static void do_set_founder(User *u, ChannelInfo *ci, char *param)
{
NickInfo *ni = findnick(param);
if (!ni) {
notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, param);
return;
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, param);
return;
}
if ((ni->channelcount >= ni->channelmax && !is_services_admin(u))
|| ni->channelcount >= MAX_CHANNELCOUNT
) {
notice_lang(s_ChanServ, u, CHAN_SET_FOUNDER_TOO_MANY_CHANS, param);
return;
}
uncount_chan(ci);
ci->founder = ni;
count_chan(ci);
if (ci->successor == ci->founder)
ci->successor = NULL;
log("%s: Changing founder of %s to %s by %s!%s@%s", s_ChanServ,
ci->name, param, u->nick, u->username, u->host);
notice_lang(s_ChanServ, u, CHAN_FOUNDER_CHANGED, ci->name, param);
}
/*************************************************************************/
static void do_set_successor(User *u, ChannelInfo *ci, char *param)
{
NickInfo *ni;
if (param) {
ni = findnick(param);
if (!ni) {
notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, param);
return;
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, param);
return;
} else if (ni == ci->founder) {
notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_IS_FOUNDER);
return;
}
} else {
ni = NULL;
}
ci->successor = ni;
if (ni)
notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_CHANGED, ci->name, param);
else
notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_UNSET, ci->name);
}
/*************************************************************************/
static void do_set_password(User *u, ChannelInfo *ci, char *param)
{
#ifdef USE_ENCRYPTION
int len = strlen(param);
int len2 = len;
if (len2 > PASSMAX) {
len2 = PASSMAX;
param[len2] = 0;
notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, PASSMAX);
}
if (encrypt(param, len2, ci->founderpass, PASSMAX) < 0) {
memset(param, 0, len);
log("%s: Failed to encrypt password for %s (set)",
s_ChanServ, ci->name);
notice_lang(s_ChanServ, u, CHAN_SET_PASSWORD_FAILED);
return;
}
memset(param, 0, len);
notice_lang(s_ChanServ, u, CHAN_PASSWORD_CHANGED, ci->name);
#else /* !USE_ENCRYPTION */
if (strlen(param) > PASSMAX-1) /* -1 for null byte */
notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, PASSMAX-1);
strscpy(ci->founderpass, param, PASSMAX);
notice_lang(s_ChanServ, u, CHAN_PASSWORD_CHANGED_TO,
ci->name, ci->founderpass);
#endif /* USE_ENCRYPTION */
if (get_access(u, ci) < ACCLEV_FOUNDER) {
log("%s: %s!%s@%s set password as Services admin for %s",
s_ChanServ, u->nick, u->username, u->host, ci->name);
if (WallSetpass)
wallops(s_ChanServ, "\2%s\2 set password as Services admin for "
"channel \2%s\2", u->nick, ci->name);
}
}
/*************************************************************************/
static void do_set_desc(User *u, ChannelInfo *ci, char *param)
{
if (ci->desc)
free(ci->desc);
ci->desc = sstrdup(param);
notice_lang(s_ChanServ, u, CHAN_DESC_CHANGED, ci->name, param);
}
/*************************************************************************/
static void do_set_url(User *u, ChannelInfo *ci, char *param)
{
if (ci->url)
free(ci->url);
if (param) {
if (!valid_url(param)) {
ci->url = NULL;
notice_lang(s_ChanServ, u, BAD_URL);
} else {
ci->url = sstrdup(param);
notice_lang(s_ChanServ, u, CHAN_URL_CHANGED, ci->name, param);
}
} else {
ci->url = NULL;
notice_lang(s_ChanServ, u, CHAN_URL_UNSET, ci->name);
}
}
/*************************************************************************/
static void do_set_email(User *u, ChannelInfo *ci, char *param)
{
if (ci->email)
free(ci->email);
if (param) {
if (!valid_email(param)) {
ci->email = NULL;
notice_lang(s_ChanServ, u, BAD_EMAIL);
} else {
ci->email = sstrdup(param);
notice_lang(s_ChanServ, u, CHAN_EMAIL_CHANGED, ci->name, param);
}
} else {
ci->email = NULL;
notice_lang(s_ChanServ, u, CHAN_EMAIL_UNSET, ci->name);
}
}
/*************************************************************************/
static void do_set_entrymsg(User *u, ChannelInfo *ci, char *param)
{
if (ci->entry_message)
free(ci->entry_message);
if (param) {
ci->entry_message = sstrdup(param);
notice_lang(s_ChanServ, u, CHAN_ENTRY_MSG_CHANGED, ci->name, param);
} else {
ci->entry_message = NULL;
notice_lang(s_ChanServ, u, CHAN_ENTRY_MSG_UNSET, ci->name);
}
}
/*************************************************************************/
static void do_set_topic(User *u, ChannelInfo *ci, char *param)
{
Channel *c = ci->c;
time_t now = time(NULL);
if (!c) {
notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, ci->name);
return;
}
set_topic(c, param, u->nick, now);
record_topic(c);
}
/*************************************************************************/
static void do_set_mlock(User *u, ChannelInfo *ci, char *param)
{
char *s, modebuf[32], *end, c;
int add = -1; /* 1 if adding, 0 if deleting, -1 if neither */
int32 newlock_on = 0, newlock_off = 0, newlock_limit = 0;
char *newlock_key = NULL;
while (*param) {
if (*param != '+' && *param != '-' && add < 0) {
param++;
continue;
}
switch ((c = *param++)) {
case '+':
add = 1;
break;
case '-':
add = 0;
break;
case 'k':
if (add) {
if (!(s = strtok(NULL, " "))) {
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_KEY_REQUIRED);
return;
}
if (newlock_key)
free(newlock_key);
newlock_key = sstrdup(s);
newlock_off &= ~CMODE_k;
} else {
if (newlock_key) {
free(newlock_key);
newlock_key = NULL;
}
newlock_off |= CMODE_k;
}
break;
case 'l':
if (add) {
if (!(s = strtok(NULL, " "))) {
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_LIMIT_REQUIRED);
return;
}
if (atol(s) <= 0) {
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_LIMIT_POSITIVE);
return;
}
newlock_limit = atol(s);
newlock_off &= ~CMODE_l;
} else {
newlock_limit = 0;
newlock_off |= CMODE_l;
}
break;
#ifdef IRC_UNREAL
case 'f':
if (add) {
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_MODE_F_BAD);
break;
}
/* fall through to default */
#endif
default: {
int32 flag = mode_char_to_flag(c, MODE_CHANNEL);
#ifdef IRC_UNREAL
if ((flag & (CMODE_A | CMODE_H))
&& !(is_services_admin(u)
|| (u->mode & (UMODE_A|UMODE_N|UMODE_T)))
) {
continue;
}
#endif
if ((flag & CMODE_OPERONLY) && !is_oper_u(u))
continue;
if (flag) {
if (flag & CMODE_REG)
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_MODE_REG_BAD, c);
else if (add)
newlock_on |= flag, newlock_off &= ~flag;
else
newlock_off |= flag, newlock_on &= ~flag;
} else {
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_UNKNOWN_CHAR, c);
}
break;
} /* default case */
} /* switch */
} /* while (*param) */
/* Now that everything's okay, actually set the new mode lock. */
ci->mlock_on = newlock_on;
ci->mlock_off = newlock_off;
ci->mlock_limit = newlock_limit;
if (ci->mlock_key)
free(ci->mlock_key);
ci->mlock_key = newlock_key;
/* Tell the user about it. */
end = modebuf;
*end = 0;
if (ci->mlock_on || ci->mlock_key || ci->mlock_limit)
end += snprintf(end, sizeof(modebuf)-(end-modebuf), "+%s%s%s",
mode_flags_to_string(ci->mlock_on, MODE_CHANNEL),
(ci->mlock_key ) ? "k" : "",
(ci->mlock_limit) ? "l" : "");
if (ci->mlock_off)
end += snprintf(end, sizeof(modebuf)-(end-modebuf), "-%s",
mode_flags_to_string(ci->mlock_off, MODE_CHANNEL));
if (*modebuf) {
notice_lang(s_ChanServ, u, CHAN_MLOCK_CHANGED, ci->name, modebuf);
} else {
notice_lang(s_ChanServ, u, CHAN_MLOCK_REMOVED, ci->name);
}
/* Implement the new lock. */
check_modes(ci->name);
}
/*************************************************************************/
static void do_set_boolean(User *u, ChannelInfo *ci, ChanOpt *co, char *param)
{
if (stricmp(param, "ON") == 0) {
ci->flags |= co->flag;
if (co->flag == CI_RESTRICTED && ci->levels[CA_NOJOIN] < 0)
ci->levels[CA_NOJOIN] = 0;
notice_lang(s_ChanServ, u, co->onstr, ci->name);
} else if (stricmp(param, "OFF") == 0) {
ci->flags &= ~co->flag;
if (co->flag == CI_RESTRICTED && ci->levels[CA_NOJOIN] >= 0)
ci->levels[CA_NOJOIN] = -1;
notice_lang(s_ChanServ, u, co->offstr, ci->name);
} else {
char buf[BUFSIZE];
snprintf(buf, sizeof(buf), "SET %s", co->name);
syntax_error(s_ChanServ, u, buf, co->syntaxstr);
}
}
/*************************************************************************/
#include "cs-access.c"
/*************************************************************************/
/* `last' is set to the last index this routine was called with */
static int akick_del(User *u, AutoKick *akick)
{
if (!akick->in_use)
return 0;
if (akick->is_nick) {
akick->u.ni = NULL;
} else {
free(akick->u.mask);
akick->u.mask = NULL;
}
if (akick->reason) {
free(akick->reason);
akick->reason = NULL;
}
akick->in_use = akick->is_nick = 0;
return 1;
}
static int akick_del_callback(User *u, int num, va_list args)
{
ChannelInfo *ci = va_arg(args, ChannelInfo *);
int *last = va_arg(args, int *);
if (num < 1 || num > ci->akickcount)
return 0;
*last = num;
return akick_del(u, &ci->akick[num-1]);
}
static int akick_list(User *u, int index, ChannelInfo *ci, int *sent_header,
int is_view)
{
AutoKick *akick = &ci->akick[index];
char buf[BUFSIZE], buf2[BUFSIZE];
if (!akick->in_use)
return 0;
if (!*sent_header) {
notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_HEADER, ci->name);
*sent_header = 1;
}
if (akick->is_nick) {
if (akick->u.ni->flags & NI_HIDE_MASK)
strscpy(buf, akick->u.ni->nick, sizeof(buf));
else
snprintf(buf, sizeof(buf), "%s (%s)",
akick->u.ni->nick, akick->u.ni->last_usermask);
} else {
strscpy(buf, akick->u.mask, sizeof(buf));
}
if (akick->reason)
snprintf(buf2, sizeof(buf2), " (%s)", akick->reason);
else
*buf2 = 0;
if (is_view)
notice_lang(s_ChanServ, u, CHAN_AKICK_VIEW_FORMAT,
index+1, buf, akick->who[0] ? akick->who : "<unknown>",
buf2);
else
notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_FORMAT, index+1, buf, buf2);
return 1;
}
static int akick_list_callback(User *u, int num, va_list args)
{
ChannelInfo *ci = va_arg(args, ChannelInfo *);
int *sent_header = va_arg(args, int *);
int is_view = va_arg(args, int);
if (num < 1 || num > ci->akickcount)
return 0;
return akick_list(u, num-1, ci, sent_header, is_view);
}
static void do_akick(User *u)
{
char *chan = strtok(NULL, " ");
char *cmd = strtok(NULL, " ");
char *mask = strtok(NULL, " ");
char *reason = strtok(NULL, "");
ChannelInfo *ci;
int i;
AutoKick *akick;
if (!cmd || (!mask &&
(stricmp(cmd, "ADD") == 0 || stricmp(cmd, "DEL") == 0))) {
syntax_error(s_ChanServ, u, "AKICK", CHAN_AKICK_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 (!check_access(u, ci, CA_AKICK) && !is_services_admin(u)) {
if (ci->founder && getlink(ci->founder) == u->ni)
notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED, s_ChanServ,
chan);
else
notice_lang(s_ChanServ, u, ACCESS_DENIED);
} else if (stricmp(cmd, "ADD") == 0) {
NickInfo *ni = findnick(mask);
char *nick, *user, *host;
if (readonly) {
notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
return;
}
if (!ni) {
split_usermask(mask, &nick, &user, &host);
mask = smalloc(strlen(nick)+strlen(user)+strlen(host)+3);
sprintf(mask, "%s!%s@%s", nick, user, host);
free(nick);
free(user);
free(host);
} else if (ni->status & NS_VERBOTEN) {
notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, mask);
return;
}
for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
if (!akick->in_use)
continue;
if (akick->is_nick ? akick->u.ni == ni
: stricmp(akick->u.mask,mask) == 0) {
notice_lang(s_ChanServ, u, CHAN_AKICK_ALREADY_EXISTS,
akick->is_nick ? akick->u.ni->nick : akick->u.mask,
chan);
return;
}
}
for (i = 0; i < ci->akickcount; i++) {
if (!ci->akick[i].in_use)
break;
}
if (i == ci->akickcount) {
if (ci->akickcount >= CSAutokickMax) {
notice_lang(s_ChanServ, u, CHAN_AKICK_REACHED_LIMIT,
CSAutokickMax);
return;
}
ci->akickcount++;
ci->akick = srealloc(ci->akick, sizeof(AutoKick) * ci->akickcount);
}
akick = &ci->akick[i];
akick->in_use = 1;
if (ni) {
akick->is_nick = 1;
akick->u.ni = ni;
} else {
akick->is_nick = 0;
akick->u.mask = mask;
}
if (reason)
akick->reason = sstrdup(reason);
else
akick->reason = NULL;
strscpy(akick->who, u->nick, NICKMAX);
notice_lang(s_ChanServ, u, CHAN_AKICK_ADDED, mask, chan);
} else if (stricmp(cmd, "DEL") == 0) {
if (readonly) {
notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
return;
}
/* Special case: is it a number/list? Only do search if it isn't. */
if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
int count, deleted, last = -1;
deleted = process_numlist(mask, &count, akick_del_callback, u,
ci, &last);
if (!deleted) {
if (count == 1) {
notice_lang(s_ChanServ, u, CHAN_AKICK_NO_SUCH_ENTRY,
last, ci->name);
} else {
notice_lang(s_ChanServ, u, CHAN_AKICK_NO_MATCH, ci->name);
}
} else if (deleted == 1) {
notice_lang(s_ChanServ, u, CHAN_AKICK_DELETED_ONE, ci->name);
} else {
notice_lang(s_ChanServ, u, CHAN_AKICK_DELETED_SEVERAL,
deleted, ci->name);
}
} else {
NickInfo *ni = findnick(mask); /* don't worry if this is NULL */
for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
if (!akick->in_use)
continue;
if ((akick->is_nick && akick->u.ni == ni)
|| (!akick->is_nick && stricmp(akick->u.mask, mask) == 0))
break;
}
if (i == ci->akickcount) {
notice_lang(s_ChanServ, u, CHAN_AKICK_NOT_FOUND, mask, chan);
return;
}
notice_lang(s_ChanServ, u, CHAN_AKICK_DELETED, mask, chan);
akick_del(u, akick);
}
} else if (stricmp(cmd, "LIST") == 0 || stricmp(cmd, "VIEW") == 0) {
int is_view = stricmp(cmd,"VIEW")==0;
int sent_header = 0;
if (ci->akickcount == 0) {
notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, chan);
return;
}
if (mask && isdigit(*mask) &&
strspn(mask, "1234567890,-") == strlen(mask)) {
process_numlist(mask, NULL, akick_list_callback, u, ci,
&sent_header, is_view);
} else {
for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
if (!akick->in_use)
continue;
if (mask) {
if (!akick->is_nick
&& !match_wild_nocase(mask, akick->u.mask))
continue;
if (akick->is_nick
&& !match_wild_nocase(mask, akick->u.ni->nick))
continue;
}
akick_list(u, i, ci, &sent_header, is_view);
}
}
if (!sent_header)
notice_lang(s_ChanServ, u, CHAN_AKICK_NO_MATCH, chan);
} else if (stricmp(cmd, "ENFORCE") == 0) {
Channel *c = findchan(ci->name);
struct c_userlist *cu = NULL;
struct c_userlist *next;
char *argv[3];
int count = 0;
if (!c) {
notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, ci->name);
return;
}
cu = c->users;
while (cu) {
next = cu->next;
if (check_kick(cu->user, c->name)) {
/* check_kick() will do the actual kick, but will not
* remove the user from the channel's user list */
argv[0] = c->name;
argv[1] = cu->user->nick;
argv[2] = CSAutokickReason;
do_kick(s_ChanServ, 3, argv);
count++;
}
cu = next;
}
notice_lang(s_ChanServ, u, CHAN_AKICK_ENFORCE_DONE, chan, count);
} else if (stricmp(cmd, "COUNT") == 0) {
int count = 0, i;
for (i = 0; i < ci->akickcount; i++) {
if (ci->akick[i].in_use)
count++;
}
notice_lang(s_ChanServ, u, CHAN_AKICK_COUNT, ci->name, count);
} else {
syntax_error(s_ChanServ, u, "AKICK", CHAN_AKICK_SYNTAX);
}
}
/*************************************************************************/
static void do_levels(User *u)
{
char *chan = strtok(NULL, " ");
char *cmd = strtok(NULL, " ");
char *what = strtok(NULL, " ");
char *s = strtok(NULL, " ");
ChannelInfo *ci;
short level;
int i;
/* If SET, we want two extra parameters; if DIS[ABLE], 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 (!is_founder(u, ci) && !is_services_admin(u)) {
notice_lang(s_ChanServ, u, ACCESS_DENIED);
} else if (stricmp(cmd, "SET") == 0) {
level = atoi(s);
if (level <= ACCLEV_INVALID || level >= ACCLEV_FOUNDER) {
notice_lang(s_ChanServ, u, CHAN_LEVELS_RANGE,
ACCLEV_INVALID+1, ACCLEV_FOUNDER-1);
return;
}
for (i = 0; levelinfo[i].what >= 0; i++) {
if (stricmp(levelinfo[i].name, what) == 0) {
ci->levels[levelinfo[i].what] = level;
notice_lang(s_ChanServ, u, CHAN_LEVELS_CHANGED,
levelinfo[i].name, chan, level);
return;
}
}
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] = ACCLEV_INVALID;
notice_lang(s_ChanServ, u, CHAN_LEVELS_DISABLED,
levelinfo[i].name, chan);
return;
}
}
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 == ACCLEV_INVALID)
notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_DISABLED,
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);
notice_lang(s_ChanServ, u, CHAN_LEVELS_RESET, chan);
} else {
syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX);
}
}
/*************************************************************************/
/* SADMINS, and users who have identified for a channel, can now cause its
* entry message and successor to be displayed by supplying the ALL
* parameter.
* Syntax: INFO channel [ALL]
* -TheShadow (29 Mar 1999)
*/
/* Check the status of show_all and make a note of having done so. See
* comments at nickserv.c/do_info() for details. */
#define CHECK_SHOW_ALL (used_all++, show_all)
static void do_info(User *u)
{
char *chan = strtok(NULL, " ");
char *param = strtok(NULL, " ");
ChannelInfo *ci;
NickInfo *ni;
char buf[BUFSIZE], *end, *s;
struct tm *tm;
int is_servadmin = is_services_admin(u);
int can_show_all = 0, show_all = 0, used_all = 0;
if (!chan) {
syntax_error(s_ChanServ, u, "INFO", CHAN_INFO_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->founder) {
/* Paranoia... this shouldn't be able to happen */
delchan(ci);
notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
} else {
/* Only show all the channel's settings to sadmins and founders. */
can_show_all = (is_identified(u, ci) || is_servadmin);
if ((param && stricmp(param, "ALL") == 0) && can_show_all)
show_all = 1;
notice_lang(s_ChanServ, u, CHAN_INFO_HEADER, chan);
ni = ci->founder;
if (ni->last_usermask && (is_servadmin || !(ni->flags & NI_HIDE_MASK)))
{
notice_lang(s_ChanServ, u, CHAN_INFO_FOUNDER, ni->nick,
ni->last_usermask);
} else {
notice_lang(s_ChanServ, u, CHAN_INFO_NO_FOUNDER, ni->nick);
}
if ((ni = ci->successor) != NULL && CHECK_SHOW_ALL) {
if (ni->last_usermask && (is_servadmin ||
!(ni->flags & NI_HIDE_MASK))) {
notice_lang(s_ChanServ, u, CHAN_INFO_SUCCESSOR, ni->nick,
ni->last_usermask);
} else {
notice_lang(s_ChanServ, u, CHAN_INFO_NO_SUCCESSOR, ni->nick);
}
}
notice_lang(s_ChanServ, u, CHAN_INFO_DESCRIPTION, ci->desc);
tm = localtime(&ci->time_registered);
strftime_lang(buf, sizeof(buf), u, STRFTIME_DATE_TIME_FORMAT, tm);
notice_lang(s_ChanServ, u, CHAN_INFO_TIME_REGGED, buf);
tm = localtime(&ci->last_used);
strftime_lang(buf, sizeof(buf), u, STRFTIME_DATE_TIME_FORMAT, tm);
notice_lang(s_ChanServ, u, CHAN_INFO_LAST_USED, buf);
/* Do not show last_topic if channel is mlock'ed +s or +p, or if the
* channel's current modes include +s or +p. -TheShadow */
if (ci->last_topic && !(ci->mlock_on & (CMODE_s | CMODE_p))) {
if (!ci->c || !(ci->c->mode & (CMODE_s | CMODE_p))) {
notice_lang(s_ChanServ, u, CHAN_INFO_LAST_TOPIC,
ci->last_topic);
notice_lang(s_ChanServ, u, CHAN_INFO_TOPIC_SET_BY,
ci->last_topic_setter);
}
}
if (ci->entry_message && CHECK_SHOW_ALL)
notice_lang(s_ChanServ, u, CHAN_INFO_ENTRYMSG, ci->entry_message);
if (ci->url)
notice_lang(s_ChanServ, u, CHAN_INFO_URL, ci->url);
if (ci->email)
notice_lang(s_ChanServ, u, CHAN_INFO_EMAIL, ci->email);
s = chanopts_to_string(ci, u->ni);
notice_lang(s_ChanServ, u, CHAN_INFO_OPTIONS,
*s ? s : getstring(u->ni, CHAN_INFO_OPT_NONE));
end = buf;
*end = 0;
if (ci->mlock_on || ci->mlock_key || ci->mlock_limit)
end += snprintf(end, sizeof(buf)-(end-buf), "+%s%s%s",
mode_flags_to_string(ci->mlock_on, MODE_CHANNEL),
(ci->mlock_key ) ? "k" : "",
(ci->mlock_limit) ? "l" : "");
if (ci->mlock_off)
end += snprintf(end, sizeof(buf)-(end-buf), "-%s",
mode_flags_to_string(ci->mlock_off, MODE_CHANNEL));
if (*buf)
notice_lang(s_ChanServ, u, CHAN_INFO_MODE_LOCK, buf);
if ((ci->flags & CI_NOEXPIRE) && CHECK_SHOW_ALL)
notice_lang(s_ChanServ, u, CHAN_INFO_NO_EXPIRE);
if (ci->suspendinfo) {
notice_lang(s_ChanServ, u, CHAN_X_SUSPENDED, chan);
if (CHECK_SHOW_ALL) {
SuspendInfo *si = ci->suspendinfo;
char timebuf[BUFSIZE], expirebuf[BUFSIZE];
tm = localtime(&si->suspended);
strftime_lang(timebuf, sizeof(timebuf), u,
STRFTIME_DATE_TIME_FORMAT, tm);
expires_in_lang(expirebuf, sizeof(expirebuf), u->ni,
si->expires);
notice_lang(s_ChanServ, u, CHAN_INFO_SUSPEND_DETAILS,
si->who, timebuf, expirebuf);
notice_lang(s_ChanServ, u, CHAN_INFO_SUSPEND_REASON,
si->reason);
}
}
if (can_show_all && !show_all && used_all)
notice_lang(s_ChanServ, u, CHAN_INFO_SHOW_ALL, s_ChanServ,
ci->name);
}
}
/*************************************************************************/
/* SADMINS can search for channels based on their CI_VERBOTEN and
* CI_NOEXPIRE flags and suspension status. This works in the same way as
* NickServ's LIST command.
* Syntax for sadmins: LIST pattern [FORBIDDEN] [NOEXPIRE] [SUSPENDED]
* Also fixed CI_PRIVATE channels being shown to non-sadmins.
* -TheShadow
*/
static void do_list(User *u)
{
char *pattern = strtok(NULL, " ");
char *keyword;
ChannelInfo *ci;
int nchans;
char buf[BUFSIZE];
int is_servadmin = is_services_admin(u);
int32 matchflags = 0; /* CI_ flags a chan must match one of to qualify */
int match_susp = 0; /* nonzero to match suspended channels */
if (CSListOpersOnly && (!u || !is_oper_u(u))) {
notice_lang(s_ChanServ, u, PERMISSION_DENIED);
return;
}
if (!pattern) {
syntax_error(s_ChanServ, u, "LIST",
is_servadmin ? CHAN_LIST_SERVADMIN_SYNTAX : CHAN_LIST_SYNTAX);
} else {
nchans = 0;
while (is_servadmin && (keyword = strtok(NULL, " "))) {
if (stricmp(keyword, "FORBIDDEN") == 0)
matchflags |= CI_VERBOTEN;
if (stricmp(keyword, "NOEXPIRE") == 0)
matchflags |= CI_NOEXPIRE;
if (stricmp(keyword, "SUSPENDED") == 0)
match_susp = 1;
}
notice_lang(s_ChanServ, u, CHAN_LIST_HEADER, pattern);
for (ci = cs_firstchan(); ci; ci = cs_nextchan()) {
if (!is_servadmin && (ci->flags & (CI_PRIVATE | CI_VERBOTEN)))
continue;
if (matchflags || match_susp) {
if (!((ci->flags & matchflags) || (ci->suspendinfo && match_susp)))
continue;
}
snprintf(buf, sizeof(buf), "%-20s %s", ci->name,
ci->desc ? ci->desc : "");
if (irc_stricmp(pattern, ci->name) == 0 || match_wild_nocase(pattern, buf)) {
if (++nchans <= CSListMax) {
char noexpire_char = ' ', suspended_char = ' ';
if (is_servadmin) {
if (ci->flags & CI_NOEXPIRE)
noexpire_char = '!';
if (ci->suspendinfo)
noexpire_char = '*';
}
/* This can only be true for SADMINS - normal users
* will never get this far with a VERBOTEN channel.
* -TheShadow */
if (ci->flags & CI_VERBOTEN) {
snprintf(buf, sizeof(buf), "%-20s [Forbidden]",
ci->name);
}
notice(s_ChanServ, u->nick, " %c%c%s",
suspended_char, noexpire_char, buf);
}
}
}
notice_lang(s_ChanServ, u, CHAN_LIST_END,
nchans>CSListMax ? CSListMax : nchans, nchans);
}
}
/*************************************************************************/
static void do_invite(User *u)
{
char *chan = strtok(NULL, " ");
Channel *c;
ChannelInfo *ci;
if (!chan) {
syntax_error(s_ChanServ, u, "INVITE", CHAN_INVITE_SYNTAX);
} else if (!(c = findchan(chan))) {
notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
} else if (c->bouncy_modes) {
notice_lang(s_ChanServ, u, CHAN_BOUNCY_MODES, "INVITE");
} else if (!(ci = c->ci)) {
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 (!u || !check_access(u, ci, CA_INVITE)) {
notice_lang(s_ChanServ, u, PERMISSION_DENIED);
} else {
send_cmd(s_ChanServ, "INVITE %s %s", u->nick, chan);
notice_lang(s_ChanServ, u, CHAN_INVITE_OK, u->nick, chan);
}
}
/*************************************************************************/
/* Internal routine to handle all op/voice-type requests. */
static struct {
const char *cmd;
int add;
int32 mode;
int sender_acc; /* Access required for the sender */
int target_acc; /* Target access level at which we refuse command */
int target_nextacc; /* Level above target_acc (voice -> halfop etc) */
int success_msg, failure_msg;
} opvoice_data[] = {
{ "OP", 1, CUMODE_o, CA_OPDEOP, CA_AUTODEOP, -1,
CHAN_OP_SUCCEEDED, CHAN_OP_FAILED },
{ "DEOP", 0, CUMODE_o, CA_OPDEOP, CA_AUTOOP, -1,
CHAN_DEOP_SUCCEEDED, CHAN_DEOP_FAILED },
{ "VOICE", 1, CUMODE_v, CA_VOICE, -1, -1,
CHAN_VOICE_SUCCEEDED, CHAN_VOICE_FAILED },
#ifndef HAVE_HALFOP
{ "DEVOICE", 0, CUMODE_v, CA_VOICE, CA_AUTOVOICE, CA_AUTOOP,
CHAN_DEVOICE_SUCCEEDED, CHAN_DEVOICE_FAILED },
#else
{ "DEVOICE", 0, CUMODE_v, CA_VOICE, CA_AUTOVOICE, CA_AUTOHALFOP,
CHAN_DEVOICE_SUCCEEDED, CHAN_DEVOICE_FAILED },
{ "HALFOP", 1, CUMODE_h, CA_HALFOP, CA_AUTODEOP, -1,
CHAN_HALFOP_SUCCEEDED, CHAN_HALFOP_FAILED },
{ "DEHALFOP", 0, CUMODE_h, CA_HALFOP, CA_AUTOHALFOP, CA_AUTOOP,
CHAN_DEHALFOP_SUCCEEDED, CHAN_DEHALFOP_FAILED },
#endif
#ifdef HAVE_CHANPROT
{ "PROTECT", 1, CUMODE_a, CA_PROTECT, -1, -1,
CHAN_PROTECT_SUCCEEDED, CHAN_PROTECT_FAILED },
{ "DEPROTECT", 0, CUMODE_a, CA_PROTECT, -1, -1,
CHAN_DEPROTECT_SUCCEEDED, CHAN_DEPROTECT_FAILED },
#endif
};
static void do_opvoice(User *u, const char *cmd)
{
char *chan = strtok(NULL, " ");
char *target = strtok(NULL, " ");
Channel *c;
ChannelInfo *ci;
User *target_user;
int i;
int add, sender_acc, target_acc, target_nextacc, success_msg, failure_msg;
int32 mode;
for (i = 0; strcmp(opvoice_data[i].cmd, cmd) != 0; i++)
;
add = opvoice_data[i].add;
mode = opvoice_data[i].mode;
sender_acc = opvoice_data[i].sender_acc;
target_acc = opvoice_data[i].target_acc;
target_nextacc = opvoice_data[i].target_nextacc;
success_msg = opvoice_data[i].success_msg;
failure_msg = opvoice_data[i].failure_msg;
if (!chan || !target) {
char buf[BUFSIZE];
snprintf(buf, sizeof(buf), getstring(u->ni, CHAN_OPVOICE_SYNTAX), cmd);
notice_lang(s_ChanServ, u, SYNTAX_ERROR, buf);
notice_lang(s_ChanServ, u, MORE_INFO, s_ChanServ, cmd);
} else if (!(c = findchan(chan))) {
notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
} else if (c->bouncy_modes) {
notice_lang(s_ChanServ, u, CHAN_BOUNCY_MODES, cmd);
} else if (!(ci = c->ci)) {
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 (!u || !check_access(u, ci, sender_acc)) {
notice_lang(s_ChanServ, u, PERMISSION_DENIED);
} else if (!(target_user = finduser(target))) {
notice_lang(s_ChanServ, u, NICK_X_NOT_IN_USE, target);
} else if (target_user != u && !(!add && !(ci->flags & CI_ENFORCE))
/* Allow changing own mode; allow deops if !ENFORCE */
&& target_acc >= 0 && check_access(target_user, ci, target_acc)
/* Disallow if user is at/above disallow level... */
&& (target_nextacc < 0
|| !check_access(target_user, ci, target_nextacc))
/* ... and below level-above-disallow-level (if any) */
) {
notice_lang(s_ChanServ, u, failure_msg, target, chan);
} else {
char *av[3];
char modebuf[3];
struct u_chanlist *c;
for (c = target_user->chans; c; c = c->next) {
if (irc_stricmp(chan, c->chan->name) == 0)
break;
}
if (!c) {
notice_lang(s_ChanServ, u, NICK_X_NOT_ON_CHAN_X, target, chan);
return;
}
modebuf[0] = add ? '+' : '-';
modebuf[1] = mode_flag_to_char(mode, MODE_CHANUSER);
modebuf[2] = 0;
send_cmode(MODE_SENDER(s_ChanServ), chan, modebuf, target);
av[0] = chan;
av[1] = modebuf;
av[2] = target;
do_cmode(s_ChanServ, 3, av);
if (ci->flags & CI_OPNOTICE) {
notice(s_ChanServ, chan, "%s command used for %s by %s",
cmd, target, u->nick);
}
notice_lang(s_ChanServ, u, success_msg, target, chan);
/* If it was an OP command, update the last-used time */
if (strcmp(cmd, "OP") == 0)
ci->last_used = time(NULL);
}
}
static void do_op(User *u)
{
do_opvoice(u, "OP");
}
static void do_deop(User *u)
{
do_opvoice(u, "DEOP");
}
static void do_voice(User *u)
{
do_opvoice(u, "VOICE");
}
static void do_devoice(User *u)
{
do_opvoice(u, "DEVOICE");
}
#ifdef HAVE_HALFOP
static void do_halfop(User *u)
{
do_opvoice(u, "HALFOP");
}
static void do_dehalfop(User *u)
{
do_opvoice(u, "DEHALFOP");
}
#endif /* HAVE_HALFOP */
#ifdef HAVE_CHANPROT
static void do_protect(User *u)
{
do_opvoice(u, "PROTECT");
}
static void do_deprotect(User *u)
{
do_opvoice(u, "DEPROTECT");
}
#endif /* HAVE_CHANPROT */
/*************************************************************************/
static void do_unban(User *u)
{
char *chan = strtok(NULL, " ");
Channel *c;
ChannelInfo *ci;
if (!chan) {
syntax_error(s_ChanServ, u, "UNBAN", CHAN_UNBAN_SYNTAX);
} else if (!(c = findchan(chan))) {
notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
} else if (c->bouncy_modes) {
notice_lang(s_ChanServ, u, CHAN_BOUNCY_MODES, "UNBAN");
} else if (!(ci = c->ci)) {
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 (!u || !check_access(u, ci, CA_UNBAN)) {
notice_lang(s_ChanServ, u, PERMISSION_DENIED);
} else {
#ifdef IRC_BAHAMUT
/* SVSMODE does this for us (note that we can't do it ourselves
* because Bahamut servers apply IP-address bans to hostnames, and
* we don't have the user's IP address) */
send_cmd(s_ChanServ, "SVSMODE %s -b %s", chan, u->nick);
#else
clear_channel(c, CLEAR_BANS, u);
#endif /* IRC_BAHAMUT */
notice_lang(s_ChanServ, u, CHAN_UNBANNED, chan);
}
}
/*************************************************************************/
static void do_clear(User *u)
{
char *chan = strtok(NULL, " ");
char *what = strtok(NULL, " ");
Channel *c;
ChannelInfo *ci;
if (!what) {
syntax_error(s_ChanServ, u, "CLEAR", CHAN_CLEAR_SYNTAX);
} else if (!(c = findchan(chan))) {
notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
} else if (c->bouncy_modes) {
notice_lang(s_ChanServ, u, CHAN_BOUNCY_MODES, "CLEAR");
} else if (!(ci = c->ci)) {
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 (!u || !check_access(u, ci, CA_CLEAR)) {
notice_lang(s_ChanServ, u, PERMISSION_DENIED);
} else if (stricmp(what, "BANS") == 0) {
clear_channel(c, CLEAR_BANS, NULL);
notice_lang(s_ChanServ, u, CHAN_CLEARED_BANS, chan);
#ifdef HAVE_BANEXCEPT
} else if (stricmp(what, "EXCEPTIONS") == 0) {
clear_channel(c, CLEAR_EXCEPTS, NULL);
notice_lang(s_ChanServ, u, CHAN_CLEARED_EXCEPTIONS, chan);
#endif
} else if (stricmp(what, "MODES") == 0) {
clear_channel(c, CLEAR_MODES, NULL);
notice_lang(s_ChanServ, u, CHAN_CLEARED_MODES, chan);
} else if (stricmp(what, "OPS") == 0) {
clear_channel(c, CLEAR_UMODES, (void *)CUMODE_o);
notice_lang(s_ChanServ, u, CHAN_CLEARED_OPS, chan);
#ifdef HAVE_HALFOP
} else if (stricmp(what, "HALFOPS") == 0) {
clear_channel(c, CLEAR_UMODES, (void *)CUMODE_h);
notice_lang(s_ChanServ, u, CHAN_CLEARED_HALFOPS, chan);
#endif
} else if (stricmp(what, "VOICES") == 0) {
clear_channel(c, CLEAR_UMODES, (void *)CUMODE_v);
notice_lang(s_ChanServ, u, CHAN_CLEARED_VOICES, chan);
} else if (stricmp(what, "USERS") == 0) {
char buf[BUFSIZE];
snprintf(buf, sizeof(buf), "CLEAR USERS command from %s", u->nick);
clear_channel(c, CLEAR_USERS, buf);
notice_lang(s_ChanServ, u, CHAN_CLEARED_USERS, chan);
} else {
syntax_error(s_ChanServ, u, "CLEAR", CHAN_CLEAR_SYNTAX);
}
}
/*************************************************************************/
/* Assumes that permission checking has already been done. */
static void do_getpass(User *u)
{
#ifdef USE_ENCRYPTION
notice_lang(s_ChanServ, u, CHAN_GETPASS_UNAVAILABLE);
#else
char *chan = strtok(NULL, " ");
ChannelInfo *ci;
if (!chan) {
syntax_error(s_ChanServ, u, "GETPASS", CHAN_GETPASS_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 {
log("%s: %s!%s@%s used GETPASS on %s",
s_ChanServ, u->nick, u->username, u->host, ci->name);
if (WallGetpass) {
wallops(s_ChanServ, "\2%s\2 used GETPASS on channel \2%s\2",
u->nick, chan);
}
notice_lang(s_ChanServ, u, CHAN_GETPASS_PASSWORD_IS,
chan, ci->founderpass);
}
#endif
}
/*************************************************************************/
static void do_forbid(User *u)
{
ChannelInfo *ci;
char *chan = strtok(NULL, " ");
/* Assumes that permission checking has already been done. */
if (!chan || *chan != '#') {
syntax_error(s_ChanServ, u, "FORBID", CHAN_FORBID_SYNTAX);
return;
}
if (readonly)
notice_lang(s_ChanServ, u, READ_ONLY_MODE);
if ((ci = cs_findchan(chan)) != NULL)
delchan(ci);
ci = makechan(chan);
if (ci) {
Channel *c;
log("%s: %s set FORBID for channel %s", s_ChanServ, u->nick,
ci->name);
ci->flags |= CI_VERBOTEN;
ci->time_registered = time(NULL);
notice_lang(s_ChanServ, u, CHAN_FORBID_SUCCEEDED, chan);
c = findchan(chan);
if (c)
clear_channel(c, CLEAR_USERS,
"Use of this channel has been forbidden");
} else {
log("%s: Valid FORBID for %s by %s failed", s_ChanServ, ci->name,
u->nick);
notice_lang(s_ChanServ, u, CHAN_FORBID_FAILED, chan);
}
}
/*************************************************************************/
static void do_suspend(User *u)
{
ChannelInfo *ci;
char *expiry, *chan, *reason;
time_t expires;
chan = strtok(NULL, " ");
if (chan && *chan == '+') {
expiry = chan;
chan = strtok(NULL, " ");
} else {
expiry = NULL;
}
reason = strtok(NULL, "");
if (!reason) {
syntax_error(s_ChanServ, u, "SUSPEND", CHAN_SUSPEND_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->suspendinfo) {
notice_lang(s_ChanServ, u, CHAN_SUSPEND_ALREADY_SUSPENDED, chan);
} else {
Channel *c;
if (expiry)
expires = dotime(expiry);
else
expires = CSSuspendExpire;
if (expires < 0) {
notice_lang(s_ChanServ, u, BAD_EXPIRY_TIME);
return;
} else if (expires > 0) {
expires += time(NULL); /* Set an absolute time */
}
log("%s: %s suspended %s", s_ChanServ, u->nick, ci->name);
suspend(ci, reason, u->nick, expires);
notice_lang(s_ChanServ, u, CHAN_SUSPEND_SUCCEEDED, chan);
c = findchan(chan);
if (c)
clear_channel(c, CLEAR_USERS,
"Use of this channel has been forbidden");
if (readonly)
notice_lang(s_ChanServ, u, READ_ONLY_MODE);
}
}
/*************************************************************************/
static void do_unsuspend(User *u)
{
ChannelInfo *ci;
char *chan = strtok(NULL, " ");
if (!chan) {
syntax_error(s_ChanServ, u, "UNSUSPEND", CHAN_UNSUSPEND_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->suspendinfo) {
notice_lang(s_ChanServ, u, CHAN_SUSPEND_NOT_SUSPENDED, chan);
} else {
if (readonly)
notice_lang(s_ChanServ, u, READ_ONLY_MODE);
log("%s: %s unsuspended %s", s_ChanServ, u->nick, ci->name);
unsuspend(ci, 1);
notice_lang(s_ChanServ, u, CHAN_UNSUSPEND_SUCCEEDED, chan);
}
}
/*************************************************************************/
static void do_status(User *u)
{
ChannelInfo *ci;
User *u2;
char *nick, *chan;
chan = strtok(NULL, " ");
nick = strtok(NULL, " ");
if (!nick || strtok(NULL, " ")) {
notice(s_ChanServ, u->nick, "STATUS ERROR Syntax error");
return;
}
if (!(ci = cs_findchan(chan))) {
char *temp = chan;
chan = nick;
nick = temp;
ci = cs_findchan(chan);
}
if (!ci) {
notice(s_ChanServ, u->nick, "STATUS ERROR Channel %s not registered",
chan);
} else if (ci->flags & CI_VERBOTEN) {
notice(s_ChanServ, u->nick, "STATUS ERROR Channel %s forbidden", chan);
return;
} else if ((u2 = finduser(nick)) != NULL) {
notice(s_ChanServ, u->nick, "STATUS %s %s %d",
chan, nick, get_access(u2, ci));
} else { /* !u2 */
notice(s_ChanServ, u->nick, "STATUS ERROR Nick %s not online", nick);
}
}
/*************************************************************************/