diff options
Diffstat (limited to 'chanserv.c')
-rw-r--r-- | chanserv.c | 6187 |
1 files changed, 6187 insertions, 0 deletions
diff --git a/chanserv.c b/chanserv.c new file mode 100644 index 000000000..796a7817a --- /dev/null +++ b/chanserv.c @@ -0,0 +1,6187 @@ +/* ChanServ functions. + * + * (C) 2003 Anope Team + * Contact us at info@anope.org + * + * Please read COPYING and README for furhter details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + * + * $Id: chanserv.c,v 1.55 2004/03/17 20:08:49 ribosome Exp $ + * + */ + +/*************************************************************************/ + +#include "services.h" +#include "pseudo.h" + +/*************************************************************************/ +/* *INDENT-OFF* */ + +ChannelInfo *chanlists[256]; + +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, ACCESS_INVALID }, + { CA_CLEAR, ACCESS_INVALID }, + { CA_UNBAN, 5 }, + { CA_OPDEOP, 5 }, + { CA_ACCESS_LIST, 1 }, + { CA_ACCESS_CHANGE, 10 }, + { CA_MEMO, 10 }, + { CA_ASSIGN, ACCESS_INVALID }, + { CA_BADWORDS, 10 }, + { CA_NOKICK, 1 }, + { CA_FANTASIA, 3 }, + { CA_SAY, 5 }, + { CA_GREET, 5 }, + { CA_VOICEME, 3 }, + { CA_VOICE, 5 }, + { CA_GETKEY, 5 }, + { CA_AUTOHALFOP, 4 }, + { CA_AUTOPROTECT, 10 }, + { CA_OPDEOPME, 5 }, + { CA_HALFOPME, 4 }, + { CA_HALFOP, 5 }, + { CA_PROTECTME, 10 }, + { CA_PROTECT, ACCESS_INVALID }, + { CA_KICKME, 5 }, + { CA_KICK, 5 }, + { CA_SIGNKICK, ACCESS_INVALID }, + { CA_BANME, 5 }, + { CA_BAN, 5 }, + { CA_TOPIC, ACCESS_INVALID }, + { CA_INFO, ACCESS_INVALID }, + { -1 } +}; + +typedef struct { + int what; + char *name; + int desc; +} LevelInfo; +static LevelInfo levelinfo[] = { + { CA_AUTODEOP, "AUTODEOP", CHAN_LEVEL_AUTODEOP }, +#ifdef HAS_HALFOP + { CA_AUTOHALFOP, "AUTOHALFOP", CHAN_LEVEL_AUTOHALFOP }, +#endif + { CA_AUTOOP, "AUTOOP", CHAN_LEVEL_AUTOOP }, +#ifdef IRC_UNREAL + { CA_AUTOPROTECT, "AUTOPROTECT", CHAN_LEVEL_AUTOPROTECT }, +#endif +#ifdef IRC_VIAGRA + { CA_AUTOPROTECT, "AUTOPROTECT", CHAN_LEVEL_AUTOPROTECT }, +#endif +#ifdef IRC_ULTIMATE3 + { CA_AUTOPROTECT, "AUTOADMIN", CHAN_LEVEL_AUTOPROTECT }, +#endif + { CA_AUTOVOICE, "AUTOVOICE", CHAN_LEVEL_AUTOVOICE }, + { CA_NOJOIN, "NOJOIN", CHAN_LEVEL_NOJOIN }, + { CA_SIGNKICK, "SIGNKICK", CHAN_LEVEL_SIGNKICK }, + + { CA_ACCESS_LIST, "ACC-LIST", CHAN_LEVEL_ACCESS_LIST }, + { CA_ACCESS_CHANGE, "ACC-CHANGE", CHAN_LEVEL_ACCESS_CHANGE }, + { CA_AKICK, "AKICK", CHAN_LEVEL_AKICK }, + { CA_SET, "SET", CHAN_LEVEL_SET }, + + { CA_BAN, "BAN", CHAN_LEVEL_BAN }, + { CA_BANME, "BANME", CHAN_LEVEL_BANME }, + { CA_CLEAR, "CLEAR", CHAN_LEVEL_CLEAR }, + { CA_GETKEY, "GETKEY", CHAN_LEVEL_GETKEY }, +#ifdef HAS_HALFOP + { CA_HALFOP, "HALFOP", CHAN_LEVEL_HALFOP }, + { CA_HALFOPME, "HALFOPME", CHAN_LEVEL_HALFOPME }, +#endif + { CA_INFO, "INFO", CHAN_LEVEL_INFO }, + { CA_KICK, "KICK", CHAN_LEVEL_KICK }, + { CA_KICKME, "KICKME", CHAN_LEVEL_KICKME }, + { CA_INVITE, "INVITE", CHAN_LEVEL_INVITE }, + { CA_OPDEOP, "OPDEOP", CHAN_LEVEL_OPDEOP }, + { CA_OPDEOPME, "OPDEOPME", CHAN_LEVEL_OPDEOPME }, +#ifdef IRC_UNREAL + { CA_PROTECT, "PROTECT", CHAN_LEVEL_PROTECT }, + { CA_PROTECTME, "PROTECTME", CHAN_LEVEL_PROTECTME }, +#endif +#ifdef IRC_VIAGRA + { CA_PROTECT, "PROTECT", CHAN_LEVEL_PROTECT }, + { CA_PROTECTME, "PROTECTME", CHAN_LEVEL_PROTECTME }, +#endif +#ifdef IRC_ULTIMATE3 + { CA_PROTECT, "ADMIN", CHAN_LEVEL_PROTECT }, + { CA_PROTECTME, "ADMINME", CHAN_LEVEL_PROTECTME }, +#endif + { CA_TOPIC, "TOPIC", CHAN_LEVEL_TOPIC }, + { CA_UNBAN, "UNBAN", CHAN_LEVEL_UNBAN }, + { CA_VOICE, "VOICE", CHAN_LEVEL_VOICE }, + { CA_VOICEME, "VOICEME", CHAN_LEVEL_VOICEME }, + + { CA_MEMO, "MEMO", CHAN_LEVEL_MEMO }, + + { CA_ASSIGN, "ASSIGN", CHAN_LEVEL_ASSIGN }, + { CA_BADWORDS, "BADWORDS", CHAN_LEVEL_BADWORDS }, + { CA_FANTASIA, "FANTASIA", CHAN_LEVEL_FANTASIA }, + { CA_GREET, "GREET", CHAN_LEVEL_GREET }, + { CA_NOKICK, "NOKICK", CHAN_LEVEL_NOKICK }, + { CA_SAY, "SAY", CHAN_LEVEL_SAY }, + + { -1 } +}; +static int levelinfo_maxwidth = 0; + +CSModeUtil csmodeutils[] = { + { "DEOP", "!deop", "-o", CI_OPNOTICE, CA_OPDEOP, CA_OPDEOPME }, + { "OP", "!op", "+o", CI_OPNOTICE, CA_OPDEOP, CA_OPDEOPME }, + { "DEVOICE", "!devoice", "-v", 0 , CA_VOICE, CA_VOICEME }, + { "VOICE", "!voice", "+v", 0 , CA_VOICE, CA_VOICEME }, +#ifdef HAS_HALFOP + { "DEHALFOP", "!dehalfop", "-h", 0 , CA_HALFOP, CA_HALFOPME }, + { "HALFOP", "!halfop", "+h", 0 , CA_HALFOP, CA_HALFOPME }, +#endif +#ifdef IRC_UNREAL + { "DEPROTECT", "!deprotect", "-a", 0 , CA_PROTECT, CA_PROTECTME }, + { "PROTECT", "!protect", "+a", 0 , CA_PROTECT, CA_PROTECTME }, +#endif +#ifdef IRC_VIAGRA + { "DEPROTECT", "!deprotect", "-a", 0 , CA_PROTECT, CA_PROTECTME }, + { "PROTECT", "!protect", "+a", 0 , CA_PROTECT, CA_PROTECTME }, +#endif +#ifdef IRC_ULTIMATE3 + { "DEPROTECT", "!deadmin", "-a", 0 , CA_PROTECT, CA_PROTECTME }, + { "PROTECT", "!admin", "+a", 0 , CA_PROTECT, CA_PROTECTME }, +#endif + + { NULL } +}; + +int xop_msgs[4][14] = { + { CHAN_AOP_SYNTAX, + CHAN_AOP_DISABLED, + CHAN_AOP_NICKS_ONLY, + CHAN_AOP_ADDED, + CHAN_AOP_MOVED, + CHAN_AOP_NO_SUCH_ENTRY, + CHAN_AOP_NOT_FOUND, + CHAN_AOP_NO_MATCH, + CHAN_AOP_DELETED, + CHAN_AOP_DELETED_ONE, + CHAN_AOP_DELETED_SEVERAL, + CHAN_AOP_LIST_EMPTY, + CHAN_AOP_LIST_HEADER, + CHAN_AOP_CLEAR + }, + { CHAN_SOP_SYNTAX, + CHAN_SOP_DISABLED, + CHAN_SOP_NICKS_ONLY, + CHAN_SOP_ADDED, + CHAN_SOP_MOVED, + CHAN_SOP_NO_SUCH_ENTRY, + CHAN_SOP_NOT_FOUND, + CHAN_SOP_NO_MATCH, + CHAN_SOP_DELETED, + CHAN_SOP_DELETED_ONE, + CHAN_SOP_DELETED_SEVERAL, + CHAN_SOP_LIST_EMPTY, + CHAN_SOP_LIST_HEADER, + CHAN_SOP_CLEAR + }, + { CHAN_VOP_SYNTAX, + CHAN_VOP_DISABLED, + CHAN_VOP_NICKS_ONLY, + CHAN_VOP_ADDED, + CHAN_VOP_MOVED, + CHAN_VOP_NO_SUCH_ENTRY, + CHAN_VOP_NOT_FOUND, + CHAN_VOP_NO_MATCH, + CHAN_VOP_DELETED, + CHAN_VOP_DELETED_ONE, + CHAN_VOP_DELETED_SEVERAL, + CHAN_VOP_LIST_EMPTY, + CHAN_VOP_LIST_HEADER, + CHAN_VOP_CLEAR + }, + { CHAN_HOP_SYNTAX, + CHAN_HOP_DISABLED, + CHAN_HOP_NICKS_ONLY, + CHAN_HOP_ADDED, + CHAN_HOP_MOVED, + CHAN_HOP_NO_SUCH_ENTRY, + CHAN_HOP_NOT_FOUND, + CHAN_HOP_NO_MATCH, + CHAN_HOP_DELETED, + CHAN_HOP_DELETED_ONE, + CHAN_HOP_DELETED_SEVERAL, + CHAN_HOP_LIST_EMPTY, + CHAN_HOP_LIST_HEADER, + CHAN_HOP_CLEAR + } +}; + +/* *INDENT-ON* */ +/*************************************************************************/ + +static void alpha_insert_chan(ChannelInfo * ci); +static ChannelInfo *makechan(const char *chan); +static int delchan(ChannelInfo * ci); +static void reset_levels(ChannelInfo * ci); +static int is_real_founder(User * user, ChannelInfo * ci); +static int is_identified(User * user, ChannelInfo * ci); +static void make_unidentified(User * u, ChannelInfo * ci); + +static int do_help(User * u); +static int do_register(User * u); +static int do_identify(User * u); +static int do_logout(User * u); +static int do_drop(User * u); +static int do_set(User * u); +static int do_set_founder(User * u, ChannelInfo * ci, char *param); +static int do_set_successor(User * u, ChannelInfo * ci, char *param); +static int do_set_password(User * u, ChannelInfo * ci, char *param); +static int do_set_desc(User * u, ChannelInfo * ci, char *param); +static int do_set_url(User * u, ChannelInfo * ci, char *param); +static int do_set_email(User * u, ChannelInfo * ci, char *param); +static int do_set_entrymsg(User * u, ChannelInfo * ci, char *param); +static int do_set_bantype(User * u, ChannelInfo * ci, char *param); +static int do_set_mlock(User * u, ChannelInfo * ci, char *param); +static int do_set_keeptopic(User * u, ChannelInfo * ci, char *param); +static int do_set_topiclock(User * u, ChannelInfo * ci, char *param); +static int do_set_private(User * u, ChannelInfo * ci, char *param); +static int do_set_secureops(User * u, ChannelInfo * ci, char *param); +static int do_set_securefounder(User * u, ChannelInfo * ci, char *param); +static int do_set_restricted(User * u, ChannelInfo * ci, char *param); +static int do_set_secure(User * u, ChannelInfo * ci, char *param); +static int do_set_signkick(User * u, ChannelInfo * ci, char *param); +static int do_set_opnotice(User * u, ChannelInfo * ci, char *param); +static int do_set_xop(User * u, ChannelInfo * ci, char *param); +static int do_set_peace(User * u, ChannelInfo * ci, char *param); +static int do_set_noexpire(User * u, ChannelInfo * ci, char *param); +static int do_xop(User * u, char *xname, int xlev, int *xmsgs); +static int do_aop(User * u); +#ifdef HAS_HALFOP +static int do_hop(User * u); +#endif +static int do_sop(User * u); +static int do_vop(User * u); +static int do_access(User * u); +static int do_akick(User * u); +static int do_info(User * u); +static int do_list(User * u); +static int do_invite(User * u); +static int do_levels(User * u); +static int do_util(User * u, CSModeUtil * util); +static int do_op(User * u); +static int do_deop(User * u); +static int do_voice(User * u); +static int do_devoice(User * u); +#ifdef HAS_HALFOP +static int do_halfop(User * u); +static int do_dehalfop(User * u); +#endif +#ifdef IRC_UNREAL +static int do_protect(User * u); +static int do_deprotect(User * u); +static int do_owner(User * u); +static int do_deowner(User * u); +#endif +#ifdef IRC_VIAGRA +static int do_protect(User * u); +static int do_deprotect(User * u); +static int do_owner(User * u); +static int do_deowner(User * u); +#endif +#ifdef IRC_ULTIMATE3 +static int do_protect(User * u); +static int do_deprotect(User * u); +#endif +static int do_cs_kick(User * u); +static int do_ban(User * u); +static int do_cs_topic(User * u); +static int do_unban(User * u); +static int do_clear(User * u); +static int do_getkey(User * u); +static int do_getpass(User * u); +static int do_sendpass(User * u); +static int do_forbid(User * u); +static int do_suspend(User * u); +static int do_unsuspend(User * u); +static int do_status(User * u); +void moduleAddChanServCmds(void); +/*************************************************************************/ +/* *INDENT-OFF* */ +void moduleAddChanServCmds(void) { + Command *c; + c = createCommand("HELP", do_help, NULL, -1, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("REGISTER", do_register, NULL, CHAN_HELP_REGISTER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("IDENTIFY", do_identify, NULL, CHAN_HELP_IDENTIFY, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("LOGOUT", do_logout, NULL, -1,CHAN_HELP_LOGOUT, CHAN_SERVADMIN_HELP_LOGOUT,CHAN_SERVADMIN_HELP_LOGOUT, CHAN_SERVADMIN_HELP_LOGOUT); addCoreCommand(CHANSERV,c); + c = createCommand("DROP", do_drop, NULL, -1,CHAN_HELP_DROP, CHAN_SERVADMIN_HELP_DROP,CHAN_SERVADMIN_HELP_DROP, CHAN_SERVADMIN_HELP_DROP); addCoreCommand(CHANSERV,c); + c = createCommand("SET", do_set, NULL, CHAN_HELP_SET,-1, CHAN_SERVADMIN_HELP_SET,CHAN_SERVADMIN_HELP_SET, CHAN_SERVADMIN_HELP_SET); addCoreCommand(CHANSERV,c); + c = createCommand("SET FOUNDER", NULL, NULL, CHAN_HELP_SET_FOUNDER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET SUCCESSOR", NULL, NULL, CHAN_HELP_SET_SUCCESSOR, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET PASSWORD", NULL, NULL, CHAN_HELP_SET_PASSWORD, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET DESC", NULL, NULL, CHAN_HELP_SET_DESC, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET URL", NULL, NULL, CHAN_HELP_SET_URL, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET EMAIL", NULL, NULL, CHAN_HELP_SET_EMAIL, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET ENTRYMSG", NULL, NULL, CHAN_HELP_SET_ENTRYMSG, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET BANTYPE", NULL, NULL, CHAN_HELP_SET_BANTYPE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET PRIVATE", NULL, NULL, CHAN_HELP_SET_PRIVATE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET KEEPTOPIC", NULL, NULL, CHAN_HELP_SET_KEEPTOPIC, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET TOPICLOCK", NULL, NULL, CHAN_HELP_SET_TOPICLOCK, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET MLOCK", NULL, NULL, CHAN_HELP_SET_MLOCK, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET RESTRICTED", NULL, NULL, CHAN_HELP_SET_RESTRICTED, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET SECURE", NULL, NULL, CHAN_HELP_SET_SECURE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET SECUREOPS", NULL, NULL, CHAN_HELP_SET_SECUREOPS, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET SECUREFOUNDER", NULL, NULL, CHAN_HELP_SET_SECUREFOUNDER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET SIGNKICK", NULL, NULL, CHAN_HELP_SET_SIGNKICK, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET OPNOTICE", NULL, NULL, CHAN_HELP_SET_OPNOTICE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET XOP", NULL, NULL, CHAN_HELP_SET_XOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET PEACE", NULL, NULL, CHAN_HELP_SET_PEACE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SET NOEXPIRE", NULL, NULL, -1, -1,CHAN_SERVADMIN_HELP_SET_NOEXPIRE,CHAN_SERVADMIN_HELP_SET_NOEXPIRE,CHAN_SERVADMIN_HELP_SET_NOEXPIRE); addCoreCommand(CHANSERV,c); + c = createCommand("AOP", do_aop, NULL, CHAN_HELP_AOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); +#ifdef HAS_HALFOP + c = createCommand("HOP", do_hop, NULL, CHAN_HELP_HOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); +#endif + c = createCommand("SOP", do_sop, NULL, CHAN_HELP_SOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("VOP", do_vop, NULL, CHAN_HELP_VOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("ACCESS", do_access, NULL, CHAN_HELP_ACCESS, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("ACCESS LEVELS", NULL, NULL, CHAN_HELP_ACCESS_LEVELS, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("AKICK", do_akick, NULL, CHAN_HELP_AKICK, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("LEVELS", do_levels, NULL, CHAN_HELP_LEVELS, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("INFO", do_info, NULL, CHAN_HELP_INFO,-1, CHAN_SERVADMIN_HELP_INFO, CHAN_SERVADMIN_HELP_INFO,CHAN_SERVADMIN_HELP_INFO); addCoreCommand(CHANSERV,c); + c = createCommand("LIST", do_list, NULL, -1,CHAN_HELP_LIST, CHAN_SERVADMIN_HELP_LIST,CHAN_SERVADMIN_HELP_LIST, CHAN_SERVADMIN_HELP_LIST); addCoreCommand(CHANSERV,c); + c = createCommand("OP", do_op, NULL, CHAN_HELP_OP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("DEOP", do_deop, NULL, CHAN_HELP_DEOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("VOICE", do_voice, NULL, CHAN_HELP_VOICE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("DEVOICE", do_devoice, NULL, CHAN_HELP_DEVOICE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); +#ifdef HAS_HALFOP + c = createCommand("HALFOP", do_halfop, NULL, CHAN_HELP_HALFOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("DEHALFOP", do_dehalfop, NULL, CHAN_HELP_DEHALFOP, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); +#endif +#ifdef IRC_UNREAL + c = createCommand("PROTECT", do_protect, NULL, CHAN_HELP_PROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("DEPROTECT",do_deprotect,NULL, CHAN_HELP_DEPROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("OWNER", do_owner, NULL, CHAN_HELP_OWNER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("DEOWNER", do_deowner, NULL, CHAN_HELP_DEOWNER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); +#endif +#ifdef IRC_VIAGRA + c = createCommand("PROTECT", do_protect, NULL, CHAN_HELP_PROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("DEPROTECT",do_deprotect,NULL, CHAN_HELP_DEPROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("OWNER", do_owner, NULL, CHAN_HELP_OWNER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("DEOWNER", do_deowner, NULL, CHAN_HELP_DEOWNER, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); +#endif +#ifdef IRC_ULTIMATE3 + c = createCommand("ADMIN", do_protect, NULL, CHAN_HELP_PROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("DEADMIN",do_deprotect,NULL, CHAN_HELP_DEPROTECT, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); +#endif + c = createCommand("KICK", do_cs_kick, NULL, CHAN_HELP_KICK, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("BAN", do_ban, NULL, CHAN_HELP_BAN, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("TOPIC", do_cs_topic, NULL, CHAN_HELP_TOPIC, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("INVITE", do_invite, NULL, CHAN_HELP_INVITE, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("UNBAN", do_unban, NULL, CHAN_HELP_UNBAN, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("CLEAR", do_clear, NULL, CHAN_HELP_CLEAR, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("GETKEY", do_getkey, NULL, CHAN_HELP_GETKEY, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("SENDPASS", do_sendpass, NULL, CHAN_HELP_SENDPASS, -1,-1,-1,-1); addCoreCommand(CHANSERV,c); + c = createCommand("GETPASS", do_getpass, is_services_admin, -1,-1, CHAN_SERVADMIN_HELP_GETPASS,CHAN_SERVADMIN_HELP_GETPASS, CHAN_SERVADMIN_HELP_GETPASS); addCoreCommand(CHANSERV,c); + c = createCommand("FORBID", do_forbid, is_services_admin, -1,-1, CHAN_SERVADMIN_HELP_FORBID,CHAN_SERVADMIN_HELP_FORBID, CHAN_SERVADMIN_HELP_FORBID); addCoreCommand(CHANSERV,c); + c = createCommand("SUSPEND", do_suspend, is_services_admin, -1,-1, CHAN_SERVADMIN_HELP_SUSPEND,CHAN_SERVADMIN_HELP_SUSPEND, CHAN_SERVADMIN_HELP_SUSPEND); addCoreCommand(CHANSERV,c); + c = createCommand("UNSUSPEND", do_unsuspend, is_services_admin, -1,-1, CHAN_SERVADMIN_HELP_UNSUSPEND,CHAN_SERVADMIN_HELP_UNSUSPEND, CHAN_SERVADMIN_HELP_UNSUSPEND); addCoreCommand(CHANSERV,c); + c = createCommand("STATUS", do_status, is_services_admin, -1,-1, CHAN_SERVADMIN_HELP_STATUS,CHAN_SERVADMIN_HELP_STATUS, CHAN_SERVADMIN_HELP_STATUS); addCoreCommand(CHANSERV,c); +} + +/* *INDENT-ON* */ +/*************************************************************************/ +/*************************************************************************/ + +/* Returns modes for mlock in a nice way. */ + +static char *get_mlock_modes(ChannelInfo * ci, int complete) +{ + static char res[BUFSIZE]; + + char *end = res; + + if (ci->mlock_on || ci->mlock_off) { + int n = 0; + CBModeInfo *cbmi = cbmodeinfos; + + if (ci->mlock_on) { + *end++ = '+'; + n++; + + do { + if (ci->mlock_on & cbmi->flag) + *end++ = cbmi->mode; + } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1); + + cbmi = cbmodeinfos; + } + + if (ci->mlock_off) { + *end++ = '-'; + n++; + + do { + if (ci->mlock_off & cbmi->flag) + *end++ = cbmi->mode; + } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1); + + cbmi = cbmodeinfos; + } + + if (ci->mlock_on && complete) { + do { + if (cbmi->csgetvalue && (ci->mlock_on & cbmi->flag)) { + char *value = cbmi->csgetvalue(ci); + + if (value) { + *end++ = ' '; + while (*value) + *end++ = *value++; + } + } + } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1); + } + } + + *end = 0; + + return res; +} + +/* Display total number of registered channels and info about each; or, if + * a specific channel is given, display information about that channel + * (like /msg ChanServ INFO <channel>). If count_only != 0, then only + * display the number of registered channels (the channel parameter is + * ignored). + */ + +void listchans(int count_only, const char *chan) +{ + int count = 0; + ChannelInfo *ci; + int i; + + if (count_only) { + + for (i = 0; i < 256; i++) { + for (ci = chanlists[i]; ci; ci = ci->next) + count++; + } + printf("%d channels registered.\n", count); + + } else if (chan) { + + struct tm *tm; + char buf[BUFSIZE]; + + if (!(ci = cs_findchan(chan))) { + printf("Channel %s not registered.\n", chan); + return; + } + if (ci->flags & CI_VERBOTEN) { + printf("Channel %s is FORBIDden.\n", ci->name); + } else { + printf("Information about channel %s:\n", ci->name); + printf(" Founder: %s\n", ci->founder->display); + printf(" Description: %s\n", ci->desc); + tm = localtime(&ci->time_registered); + strftime(buf, sizeof(buf), + getstring(NULL, STRFTIME_DATE_TIME_FORMAT), tm); + printf(" Registered: %s\n", buf); + tm = localtime(&ci->last_used); + strftime(buf, sizeof(buf), + getstring(NULL, STRFTIME_DATE_TIME_FORMAT), tm); + printf(" Last used: %s\n", buf); + if (ci->last_topic) { + printf(" Last topic: %s\n", ci->last_topic); + printf(" Topic set by: %s\n", ci->last_topic_setter); + } + if (ci->url) + printf(" URL: %s\n", ci->url); + if (ci->email) + printf(" E-mail address: %s\n", ci->email); + printf(" Options: "); + if (!ci->flags) { + printf("None\n"); + } else { + int need_comma = 0; + static const char commastr[] = ", "; + if (ci->flags & CI_PRIVATE) { + printf("Private"); + need_comma = 1; + } + if (ci->flags & CI_KEEPTOPIC) { + printf("%sTopic Retention", + need_comma ? commastr : ""); + need_comma = 1; + } + if (ci->flags & CI_TOPICLOCK) { + printf("%sTopic Lock", need_comma ? commastr : ""); + need_comma = 1; + } + if (ci->flags & CI_SECUREOPS) { + printf("%sSecure Ops", need_comma ? commastr : ""); + need_comma = 1; + } + if (ci->flags & CI_RESTRICTED) { + printf("%sRestricted Access", + need_comma ? commastr : ""); + need_comma = 1; + } + if (ci->flags & CI_SECURE) { + printf("%sSecure", need_comma ? commastr : ""); + need_comma = 1; + } + if (ci->flags & CI_NO_EXPIRE) { + printf("%sNo Expire", need_comma ? commastr : ""); + need_comma = 1; + } + printf("\n"); + } + if (ci->mlock_on || ci->mlock_off) + printf(" Mode lock: %s\n", get_mlock_modes(ci, 1)); + } + + } else { + + for (i = 0; i < 256; i++) { + for (ci = chanlists[i]; ci; ci = ci->next) { + printf(" %s %-20s %s\n", + ci->flags & CI_NO_EXPIRE ? "!" : " ", ci->name, + ci-> + flags & CI_VERBOTEN ? "Disallowed (FORBID)" : ci-> + desc); + count++; + } + } + printf("%d channels registered.\n", count); + + } +} + +/*************************************************************************/ + +/* Return information on memory use. Assumes pointers are valid. */ + +void get_chanserv_stats(long *nrec, long *memuse) +{ + long count = 0, mem = 0; + int i, j; + ChannelInfo *ci; + + for (i = 0; i < 256; i++) { + for (ci = chanlists[i]; ci; ci = ci->next) { + 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 (j = 0; j < ci->akickcount; j++) { + if (!(ci->akick[j].flags & AK_ISNICK) + && ci->akick[j].u.mask) + mem += strlen(ci->akick[j].u.mask) + 1; + if (ci->akick[j].reason) + mem += strlen(ci->akick[j].reason) + 1; + if (ci->akick[j].creator) + mem += strlen(ci->akick[j].creator) + 1; + } + if (ci->mlock_key) + mem += strlen(ci->mlock_key) + 1; +#ifdef HAS_FMODE + if (ci->mlock_flood) + mem += strlen(ci->mlock_flood) + 1; +#endif +#ifdef HAS_LMODE + if (ci->mlock_redirect) + mem += strlen(ci->mlock_redirect) + 1; +#endif + if (ci->last_topic) + mem += strlen(ci->last_topic) + 1; + if (ci->entry_message) + mem += strlen(ci->entry_message) + 1; + if (ci->forbidby) + mem += strlen(ci->forbidby) + 1; + if (ci->forbidreason) + mem += strlen(ci->forbidreason) + 1; + if (ci->levels) + mem += sizeof(*ci->levels) * CA_SIZE; + mem += ci->memos.memocount * sizeof(Memo); + for (j = 0; j < ci->memos.memocount; j++) { + if (ci->memos.memos[j].text) + mem += strlen(ci->memos.memos[j].text) + 1; + } + if (ci->ttb) + mem += sizeof(*ci->ttb) * TTB_SIZE; + mem += ci->bwcount * sizeof(BadWord); + for (j = 0; j < ci->bwcount; j++) + if (ci->badwords[j].word) + mem += strlen(ci->badwords[j].word) + 1; + } + } + *nrec = count; + *memuse = mem; +} + +/*************************************************************************/ +/*************************************************************************/ + +/* ChanServ initialization. */ + +void cs_init(void) +{ + Command *cmd; + moduleAddChanServCmds(); + cmd = findCommand(CHANSERV, "REGISTER"); + if (cmd) + cmd->help_param1 = s_NickServ; + cmd = findCommand(CHANSERV, "SET SECURE"); + if (cmd) + cmd->help_param1 = s_NickServ; + cmd = findCommand(CHANSERV, "SET SUCCESSOR"); + if (cmd) + cmd->help_param1 = (char *) (long) CSMaxReg; +} + +/*************************************************************************/ + +/* Main ChanServ routine. */ + +void chanserv(User * u, char *buf) +{ + char *cmd, *s; + + cmd = strtok(buf, " "); + + if (!cmd) { + return; + } else if (stricmp(cmd, "\1PING") == 0) { + if (!(s = strtok(NULL, ""))) + s = "\1"; + notice(s_ChanServ, u->nick, "\1PING %s", s); + } else if (skeleton) { + notice_lang(s_ChanServ, u, SERVICE_OFFLINE, s_ChanServ); + } else { + mod_run_cmd(s_ChanServ, u, CHANSERV, cmd); + } +} + +/*************************************************************************/ + +/* Load/save data files. */ + + +#define SAFE(x) do { \ + if ((x) < 0) { \ + if (!forceload) \ + fatal("Read error on %s", ChanDBName); \ + failed = 1; \ + break; \ + } \ +} while (0) + +void load_cs_dbase(void) +{ + dbFILE *f; + int ver, i, j, c; + ChannelInfo *ci, **last, *prev; + int failed = 0; + + if (!(f = open_db(s_ChanServ, ChanDBName, "r", CHAN_VERSION))) + return; + + ver = get_file_version(f); + + for (i = 0; i < 256 && !failed; i++) { + int16 tmp16; + int32 tmp32; + int n_levels; + char *s; + NickAlias *na; + + last = &chanlists[i]; + prev = NULL; + while ((c = getc_db(f)) != 0) { + if (c != 1) + fatal("Invalid format in %s", ChanDBName); + ci = scalloc(sizeof(ChannelInfo), 1); + *last = ci; + last = &ci->next; + ci->prev = prev; + prev = ci; + SAFE(read_buffer(ci->name, f)); + SAFE(read_string(&s, f)); + if (s) { + if (ver >= 13) + ci->founder = findcore(s); + else { + na = findnick(s); + if (na) + ci->founder = na->nc; + else + ci->founder = NULL; + } + free(s); + } else + ci->founder = NULL; + if (ver >= 7) { + SAFE(read_string(&s, f)); + if (s) { + if (ver >= 13) + ci->successor = findcore(s); + else { + na = findnick(s); + if (na) + ci->successor = na->nc; + else + ci->successor = NULL; + } + free(s); + } else + ci->successor = NULL; + } else { + ci->successor = NULL; + } + SAFE(read_buffer(ci->founderpass, f)); + SAFE(read_string(&ci->desc, f)); + if (!ci->desc) + ci->desc = sstrdup(""); + SAFE(read_string(&ci->url, f)); + SAFE(read_string(&ci->email, f)); + SAFE(read_int32(&tmp32, f)); + ci->time_registered = tmp32; + SAFE(read_int32(&tmp32, f)); + ci->last_used = tmp32; + SAFE(read_string(&ci->last_topic, f)); + SAFE(read_buffer(ci->last_topic_setter, f)); + SAFE(read_int32(&tmp32, f)); + ci->last_topic_time = tmp32; + SAFE(read_int32(&ci->flags, f)); +#ifdef USE_ENCRYPTION + if (!(ci->flags & (CI_ENCRYPTEDPW | CI_VERBOTEN))) { + if (debug) + alog("debug: %s: encrypting password for %s on load", + s_ChanServ, ci->name); + if (encrypt_in_place(ci->founderpass, PASSMAX) < 0) + fatal("%s: load database: Can't encrypt %s password!", + s_ChanServ, ci->name); + ci->flags |= CI_ENCRYPTEDPW; + } +#else + if (ci->flags & CI_ENCRYPTEDPW) { + /* Bail: it makes no sense to continue with encrypted + * passwords, since we won't be able to verify them */ + fatal("%s: load database: password for %s encrypted " + "but encryption disabled, aborting", + s_ChanServ, ci->name); + } +#endif + /* Leaveops cleanup */ + if (ver <= 13 && (ci->flags & 0x00000020)) + ci->flags &= ~0x00000020; + /* Temporary flags cleanup */ + ci->flags &= ~CI_INHABIT; + + if (ver >= 9) { + SAFE(read_string(&ci->forbidby, f)); + SAFE(read_string(&ci->forbidreason, f)); + } else { + ci->forbidreason = NULL; + ci->forbidby = NULL; + } + if (ver >= 9) + SAFE(read_int16(&tmp16, f)); + else + tmp16 = CSDefBantype; + ci->bantype = tmp16; + SAFE(read_int16(&tmp16, f)); + n_levels = tmp16; + ci->levels = scalloc(2 * CA_SIZE, 1); + reset_levels(ci); + for (j = 0; j < n_levels; j++) { + if (j < CA_SIZE) + SAFE(read_int16(&ci->levels[j], f)); + else + SAFE(read_int16(&tmp16, f)); + } + /* To avoid levels list silly hacks */ + if (ver < 10) + ci->levels[CA_OPDEOPME] = ci->levels[CA_OPDEOP]; + if (ver < 11) { + ci->levels[CA_KICKME] = ci->levels[CA_OPDEOP]; + ci->levels[CA_KICK] = ci->levels[CA_OPDEOP]; + } + if (ver < 15) { + + /* Old Ultimate levels import */ + /* We now conveniently use PROTECT internals for Ultimate's ADMIN support - ShadowMaster */ + /* Doh, must of course be done before we change the values were trying to import - ShadowMaster */ + ci->levels[CA_AUTOPROTECT] = ci->levels[32]; + ci->levels[CA_PROTECTME] = ci->levels[33]; + ci->levels[CA_PROTECT] = ci->levels[34]; + + ci->levels[CA_BANME] = ci->levels[CA_OPDEOP]; + ci->levels[CA_BAN] = ci->levels[CA_OPDEOP]; + ci->levels[CA_TOPIC] = ACCESS_INVALID; + + + } + + SAFE(read_int16(&ci->accesscount, f)); + if (ci->accesscount) { + ci->access = scalloc(ci->accesscount, sizeof(ChanAccess)); + for (j = 0; j < ci->accesscount; j++) { + SAFE(read_int16(&ci->access[j].in_use, f)); + if (ci->access[j].in_use) { + SAFE(read_int16(&ci->access[j].level, f)); + SAFE(read_string(&s, f)); + if (s) { + if (ver >= 13) + ci->access[j].nc = findcore(s); + else { + na = findnick(s); + if (na) + ci->access[j].nc = na->nc; + else + ci->access[j].nc = NULL; + } + free(s); + } + if (ci->access[j].nc == NULL) + ci->access[j].in_use = 0; + if (ver >= 11) { + SAFE(read_int32(&tmp32, f)); + ci->access[j].last_seen = tmp32; + } else { + ci->access[j].last_seen = 0; /* Means we have never seen the user */ + } + } + } + } else { + ci->access = NULL; + } + + SAFE(read_int16(&ci->akickcount, f)); + if (ci->akickcount) { + ci->akick = scalloc(ci->akickcount, sizeof(AutoKick)); + for (j = 0; j < ci->akickcount; j++) { + if (ver >= 15) { + SAFE(read_int16(&ci->akick[j].flags, f)); + } else { + SAFE(read_int16(&tmp16, f)); + if (tmp16) + ci->akick[j].flags |= AK_USED; + } + if (ci->akick[j].flags & AK_USED) { + if (ver < 15) { + SAFE(read_int16(&tmp16, f)); + if (tmp16) + ci->akick[j].flags |= AK_ISNICK; + } + SAFE(read_string(&s, f)); + if (ci->akick[j].flags & AK_ISNICK) { + if (ver >= 13) { + ci->akick[j].u.nc = findcore(s); + } else { + na = findnick(s); + if (na) + ci->akick[j].u.nc = na->nc; + else + ci->akick[j].u.nc = NULL; + } + if (!ci->akick[j].u.nc) + ci->akick[j].flags &= ~AK_USED; + free(s); + } else { + ci->akick[j].u.mask = s; + } + SAFE(read_string(&s, f)); + if (ci->akick[j].flags & AK_USED) + ci->akick[j].reason = s; + else if (s) + free(s); + if (ver >= 9) { + SAFE(read_string(&s, f)); + if (ci->akick[j].flags & AK_USED) { + ci->akick[j].creator = s; + } else if (s) { + free(s); + } + SAFE(read_int32(&tmp32, f)); + if (ci->akick[j].flags & AK_USED) + ci->akick[j].addtime = tmp32; + } else { + ci->akick[j].creator = NULL; + ci->akick[j].addtime = 0; + } + } + + /* Bugfix */ + if ((ver == 15) && ci->akick[j].flags > 8) { + ci->akick[j].flags = 0; + ci->akick[j].u.nc = NULL; + ci->akick[j].u.nc = NULL; + ci->akick[j].addtime = 0; + ci->akick[j].creator = NULL; + ci->akick[j].reason = NULL; + } + } + } else { + ci->akick = NULL; + } + + if (ver >= 10) { + SAFE(read_int32(&ci->mlock_on, f)); + SAFE(read_int32(&ci->mlock_off, f)); + } else { + SAFE(read_int16(&tmp16, f)); + ci->mlock_on = tmp16; + SAFE(read_int16(&tmp16, f)); + ci->mlock_off = tmp16; + } + SAFE(read_int32(&ci->mlock_limit, f)); + SAFE(read_string(&ci->mlock_key, f)); + if (ver >= 10) { +#ifdef HAS_FMODE + SAFE(read_string(&ci->mlock_flood, f)); +#else + SAFE(read_string(&s, f)); + if (s) + free(s); +#endif +#ifdef HAS_LMODE + SAFE(read_string(&ci->mlock_redirect, f)); +#else + SAFE(read_string(&s, f)); + if (s) + free(s); +#endif + } + + SAFE(read_int16(&ci->memos.memocount, f)); + SAFE(read_int16(&ci->memos.memomax, f)); + if (ci->memos.memocount) { + Memo *memos; + memos = scalloc(sizeof(Memo) * ci->memos.memocount, 1); + ci->memos.memos = memos; + for (j = 0; j < ci->memos.memocount; j++, memos++) { + SAFE(read_int32(&memos->number, f)); + SAFE(read_int16(&memos->flags, f)); + SAFE(read_int32(&tmp32, f)); + memos->time = tmp32; + SAFE(read_buffer(memos->sender, f)); + SAFE(read_string(&memos->text, f)); + } + } + + SAFE(read_string(&ci->entry_message, f)); + + ci->c = NULL; + + /* Some cleanup */ + if (ver <= 11) { + /* Cleanup: Founder must be != than successor */ + if (!(ci->flags & CI_VERBOTEN) + && ci->successor == ci->founder) { + alog("Warning: founder and successor of %s are equal. Cleaning up.", ci->name); + ci->successor = NULL; + } + } + + /* BotServ options */ + + if (ver >= 8) { + int n_ttb; + + SAFE(read_string(&s, f)); + if (s) { + ci->bi = findbot(s); + free(s); + } else + ci->bi = NULL; + + SAFE(read_int32(&tmp32, f)); + ci->botflags = tmp32; + SAFE(read_int16(&tmp16, f)); + n_ttb = tmp16; + ci->ttb = scalloc(2 * TTB_SIZE, 1); + for (j = 0; j < n_ttb; j++) { + if (j < TTB_SIZE) + SAFE(read_int16(&ci->ttb[j], f)); + else + SAFE(read_int16(&tmp16, f)); + } + for (j = n_ttb; j < TTB_SIZE; j++) + ci->ttb[j] = 0; + SAFE(read_int16(&tmp16, f)); + ci->capsmin = tmp16; + SAFE(read_int16(&tmp16, f)); + ci->capspercent = tmp16; + SAFE(read_int16(&tmp16, f)); + ci->floodlines = tmp16; + SAFE(read_int16(&tmp16, f)); + ci->floodsecs = tmp16; + SAFE(read_int16(&tmp16, f)); + ci->repeattimes = tmp16; + + SAFE(read_int16(&ci->bwcount, f)); + if (ci->bwcount) { + ci->badwords = scalloc(ci->bwcount, sizeof(BadWord)); + for (j = 0; j < ci->bwcount; j++) { + SAFE(read_int16(&ci->badwords[j].in_use, f)); + if (ci->badwords[j].in_use) { + SAFE(read_string(&ci->badwords[j].word, f)); + SAFE(read_int16(&ci->badwords[j].type, f)); + } + } + } else { + ci->badwords = NULL; + } + } else { + ci->bi = NULL; + ci->botflags = 0; + ci->ttb = scalloc(2 * TTB_SIZE, 1); + for (j = 0; j < TTB_SIZE; j++) + ci->ttb[j] = 0; + ci->bwcount = 0; + ci->badwords = NULL; + } + + } /* while (getc_db(f) != 0) */ + + *last = NULL; + + } /* for (i) */ + + close_db(f); + + /* Check for non-forbidden channels with no founder. + Makes also other essential tasks. */ + for (i = 0; i < 256; i++) { + ChannelInfo *next; + for (ci = chanlists[i]; ci; ci = next) { + next = ci->next; + if (!(ci->flags & CI_VERBOTEN) && !ci->founder) { + alog("%s: database load: Deleting founderless channel %s", + s_ChanServ, ci->name); + delchan(ci); + continue; + } + if (ver < 13) { + ChanAccess *access, *access2; + AutoKick *akick, *akick2; + int k; + + if (ci->flags & CI_VERBOTEN) + continue; + /* Need to regenerate the channel count for the founder */ + ci->founder->channelcount++; + /* Check for eventual double entries in access/akick lists. */ + for (j = 0, access = ci->access; j < ci->accesscount; + j++, access++) { + if (!access->in_use) + continue; + for (k = 0, access2 = ci->access; k < j; + k++, access2++) { + if (access2->in_use && access2->nc == access->nc) { + alog("%s: deleting %s channel access entry of %s because it is already in the list (this is OK).", s_ChanServ, access->nc->display, ci->name); + memset(access, 0, sizeof(ChanAccess)); + break; + } + } + } + for (j = 0, akick = ci->akick; j < ci->akickcount; + j++, akick++) { + if (!(akick->flags & AK_USED) + || !(akick->flags & AK_ISNICK)) + continue; + for (k = 0, akick2 = ci->akick; k < j; k++, akick2++) { + if ((akick2->flags & AK_USED) + && (akick2->flags & AK_ISNICK) + && akick2->u.nc == akick->u.nc) { + alog("%s: deleting %s channel akick entry of %s because it is already in the list (this is OK).", s_ChanServ, akick->u.nc->display, ci->name); + if (akick->reason) + free(akick->reason); + if (akick->creator) + free(akick->creator); + memset(akick, 0, sizeof(AutoKick)); + break; + } + } + } + } + } + } +} + +#undef SAFE + +/*************************************************************************/ + +#define SAFE(x) do { \ + if ((x) < 0) { \ + restore_db(f); \ + log_perror("Write error on %s", ChanDBName); \ + if (time(NULL) - lastwarn > WarningTimeout) { \ + wallops(NULL, "Write error on %s: %s", ChanDBName, \ + strerror(errno)); \ + lastwarn = time(NULL); \ + } \ + return; \ + } \ +} while (0) + +void save_cs_dbase(void) +{ + dbFILE *f; + int i, j; + ChannelInfo *ci; + Memo *memos; + static time_t lastwarn = 0; + + if (!(f = open_db(s_ChanServ, ChanDBName, "w", CHAN_VERSION))) + return; + + for (i = 0; i < 256; i++) { + int16 tmp16; + + for (ci = chanlists[i]; ci; ci = ci->next) { + SAFE(write_int8(1, f)); + SAFE(write_buffer(ci->name, f)); + if (ci->founder) + SAFE(write_string(ci->founder->display, f)); + else + SAFE(write_string(NULL, f)); + if (ci->successor) + SAFE(write_string(ci->successor->display, f)); + else + SAFE(write_string(NULL, f)); + SAFE(write_buffer(ci->founderpass, f)); + SAFE(write_string(ci->desc, f)); + SAFE(write_string(ci->url, f)); + SAFE(write_string(ci->email, f)); + SAFE(write_int32(ci->time_registered, f)); + SAFE(write_int32(ci->last_used, f)); + SAFE(write_string(ci->last_topic, f)); + SAFE(write_buffer(ci->last_topic_setter, f)); + SAFE(write_int32(ci->last_topic_time, f)); + SAFE(write_int32(ci->flags, f)); + SAFE(write_string(ci->forbidby, f)); + SAFE(write_string(ci->forbidreason, f)); + SAFE(write_int16(ci->bantype, f)); + + tmp16 = CA_SIZE; + SAFE(write_int16(tmp16, f)); + for (j = 0; j < CA_SIZE; j++) + SAFE(write_int16(ci->levels[j], f)); + + SAFE(write_int16(ci->accesscount, f)); + for (j = 0; j < ci->accesscount; j++) { + SAFE(write_int16(ci->access[j].in_use, f)); + if (ci->access[j].in_use) { + SAFE(write_int16(ci->access[j].level, f)); + SAFE(write_string(ci->access[j].nc->display, f)); + SAFE(write_int32(ci->access[j].last_seen, f)); + } + } + + SAFE(write_int16(ci->akickcount, f)); + for (j = 0; j < ci->akickcount; j++) { + SAFE(write_int16(ci->akick[j].flags, f)); + if (ci->akick[j].flags & AK_USED) { + if (ci->akick[j].flags & AK_ISNICK) + SAFE(write_string(ci->akick[j].u.nc->display, f)); + else + SAFE(write_string(ci->akick[j].u.mask, f)); + SAFE(write_string(ci->akick[j].reason, f)); + SAFE(write_string(ci->akick[j].creator, f)); + SAFE(write_int32(ci->akick[j].addtime, f)); + } + } + + SAFE(write_int32(ci->mlock_on, f)); + SAFE(write_int32(ci->mlock_off, f)); + SAFE(write_int32(ci->mlock_limit, f)); + SAFE(write_string(ci->mlock_key, f)); +#ifdef HAS_FMODE + SAFE(write_string(ci->mlock_flood, f)); +#else + SAFE(write_string(NULL, f)); +#endif +#ifdef HAS_LMODE + SAFE(write_string(ci->mlock_redirect, f)); +#else + SAFE(write_string(NULL, f)); +#endif + + SAFE(write_int16(ci->memos.memocount, f)); + SAFE(write_int16(ci->memos.memomax, f)); + memos = ci->memos.memos; + for (j = 0; j < ci->memos.memocount; j++, memos++) { + SAFE(write_int32(memos->number, f)); + SAFE(write_int16(memos->flags, f)); + SAFE(write_int32(memos->time, f)); + SAFE(write_buffer(memos->sender, f)); + SAFE(write_string(memos->text, f)); + } + + SAFE(write_string(ci->entry_message, f)); + + if (ci->bi) + SAFE(write_string(ci->bi->nick, f)); + else + SAFE(write_string(NULL, f)); + + SAFE(write_int32(ci->botflags, f)); + + tmp16 = TTB_SIZE; + SAFE(write_int16(tmp16, f)); + for (j = 0; j < TTB_SIZE; j++) + SAFE(write_int16(ci->ttb[j], f)); + + SAFE(write_int16(ci->capsmin, f)); + SAFE(write_int16(ci->capspercent, f)); + SAFE(write_int16(ci->floodlines, f)); + SAFE(write_int16(ci->floodsecs, f)); + SAFE(write_int16(ci->repeattimes, f)); + + SAFE(write_int16(ci->bwcount, f)); + for (j = 0; j < ci->bwcount; j++) { + SAFE(write_int16(ci->badwords[j].in_use, f)); + if (ci->badwords[j].in_use) { + SAFE(write_string(ci->badwords[j].word, f)); + SAFE(write_int16(ci->badwords[j].type, f)); + } + } + } /* for (chanlists[i]) */ + + SAFE(write_int8(0, f)); + + } /* for (i) */ + + close_db(f); + +} + +#undef SAFE + +/*************************************************************************/ + +void save_cs_rdb_dbase(void) +{ +#ifdef USE_RDB + int i; + ChannelInfo *ci; + + if (!rdb_open()) + return; + + rdb_tag_table("anope_cs_info"); + rdb_scrub_table("anope_ms_info", "serv='CHAN'"); + rdb_clear_table("anope_cs_access"); + rdb_clear_table("anope_cs_levels"); + rdb_clear_table("anope_cs_akicks"); + rdb_clear_table("anope_cs_badwords"); + + for (i = 0; i < 256; i++) { + for (ci = chanlists[i]; ci; ci = ci->next) { + rdb_save_cs_info(ci); + } /* for (chanlists[i]) */ + } /* for (i) */ + + rdb_scrub_table("anope_cs_info", "active='0'"); + rdb_close(); +#endif +} + +/*************************************************************************/ + +/* Check the current modes on a channel; if they conflict with a mode lock, + * fix them. */ + +void check_modes(Channel * c) +{ + char modebuf[64], argbuf[BUFSIZE], *end = modebuf, *end2 = argbuf; + uint32 modes; + ChannelInfo *ci; + CBModeInfo *cbmi; + CBMode *cbm; + + if (c->bouncy_modes) + return; + + /* Check for mode bouncing */ + if (c->server_modecount >= 3 && c->chanserv_modecount >= 3) { + wallops(NULL, "Warning: unable to set modes on channel %s. " + "Are your servers' U:lines configured correctly?", + c->name); + alog("%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++; + + if (!(ci = c->ci)) { +#ifndef IRC_HYBRID + if (c->mode & CMODE_r) { + c->mode &= ~CMODE_r; + send_cmd(whosends(ci), "MODE %s -r", c->name); + } +#endif + return; + } + + modes = ~c->mode & ci->mlock_on; + + *end++ = '+'; + cbmi = cbmodeinfos; + + do { + if (modes & cbmi->flag) { + *end++ = cbmi->mode; + c->mode |= cbmi->flag; + + /* Add the eventual parameter and modify the Channel structure */ + if (cbmi->getvalue && cbmi->csgetvalue) { + char *value = cbmi->csgetvalue(ci); + + cbm = &cbmodes[(int) cbmi->mode]; + cbm->setvalue(c, value); + + if (value) { + *end2++ = ' '; + while (*value) + *end2++ = *value++; + } + } + } else if (cbmi->getvalue && cbmi->csgetvalue + && (ci->mlock_on & cbmi->flag) + && (c->mode & cbmi->flag)) { + char *value = cbmi->getvalue(c); + char *csvalue = cbmi->csgetvalue(ci); + + /* Lock and actual values don't match, so fix the mode */ + if (value && csvalue && strcmp(value, csvalue)) { + *end++ = cbmi->mode; + + cbm = &cbmodes[(int) cbmi->mode]; + cbm->setvalue(c, csvalue); + + *end2++ = ' '; + while (*csvalue) + *end2++ = *csvalue++; + } + } + } while ((++cbmi)->flag != 0); + + if (*(end - 1) == '+') + end--; + + modes = c->mode & ci->mlock_off; + + if (modes) { + *end++ = '-'; + cbmi = cbmodeinfos; + + do { + if (modes & cbmi->flag) { + *end++ = cbmi->mode; + c->mode &= ~cbmi->flag; + + /* Add the eventual parameter and clean up the Channel structure */ + if (cbmi->getvalue) { + cbm = &cbmodes[(int) cbmi->mode]; + + if (!(cbm->flags & CBM_MINUS_NO_ARG)) { + char *value = cbmi->getvalue(c); + + if (value) { + *end2++ = ' '; + while (*value) + *end2++ = *value++; + } + } + + cbm->setvalue(c, NULL); + } + } + } while ((++cbmi)->flag != 0); + } + + if (end == modebuf) + return; + + *end = 0; + *end2 = 0; + + send_cmd(whosends(ci), "MODE %s %s%s", c->name, modebuf, + (end2 == argbuf ? "" : argbuf)); +} + +/*************************************************************************/ + +#ifdef IRC_ULTIMATE3 + + +int check_valid_admin(User * user, Channel * chan, int servermode) +{ + if (!chan->ci) + return 1; + + /* They will be kicked; no need to deop, no need to update our internal struct too */ + if (chan->ci->flags & CI_VERBOTEN) + return 0; + + if (servermode && !check_access(user, chan->ci, CA_AUTOPROTECT)) { + notice_lang(s_ChanServ, user, CHAN_IS_REGISTERED, s_ChanServ); + send_cmd(whosends(chan->ci), "MODE %s -a %s", chan->name, + user->nick); + return 0; + } + + if (check_access(user, chan->ci, CA_AUTODEOP)) { + send_cmd(whosends(chan->ci), "MODE %s -a %s", chan->name, + user->nick); + return 0; + } + + return 1; +} +#endif + +/*************************************************************************/ + +/* Check whether a user is allowed to be opped on a channel; if they + * aren't, deop them. If serverop is 1, the +o was done by a server. + * Return 1 if the user is allowed to be opped, 0 otherwise. */ + +int check_valid_op(User * user, Channel * chan, int servermode) +{ + if (!chan->ci) + return 1; + + /* They will be kicked; no need to deop, no need to update our internal struct too */ + if (chan->ci->flags & CI_VERBOTEN) + return 0; + + if (servermode && !check_access(user, chan->ci, CA_AUTOOP)) { + notice_lang(s_ChanServ, user, CHAN_IS_REGISTERED, s_ChanServ); +#ifdef HAS_HALFOP +# if defined(IRC_UNREAL) + if (check_access(user, chan->ci, CA_AUTOHALFOP)) { + send_cmd(whosends(chan->ci), "MODE %s -aoq %s %s %s", + chan->name, user->nick, user->nick, user->nick); + } else { + send_cmd(whosends(chan->ci), "MODE %s -ahoq %s %s %s %s", + chan->name, user->nick, user->nick, user->nick, + user->nick); + } +# elif defined(IRC_ULTIMATE3) + if (check_access(user, chan->ci, CA_AUTOHALFOP)) { + send_cmd(whosends(chan->ci), "MODE %s -ao %s %s", + chan->name, user->nick, user->nick); + } else { + send_cmd(whosends(chan->ci), "MODE %s -aoh %s %s %s", + chan->name, user->nick, user->nick, user->nick); + } +# else + if (check_access(user, chan->ci, CA_AUTOHALFOP)) { + send_cmd(whosends(chan->ci), "MODE %s -o %s", chan->name, + user->nick); + } else { + send_cmd(whosends(chan->ci), "MODE %s -ho %s %s", chan->name, + user->nick, user->nick); + } +# endif +#else + send_cmd(whosends(chan->ci), "MODE %s -o %s", chan->name, + user->nick); +#endif + return 0; + } + + if (check_access(user, chan->ci, CA_AUTODEOP)) { +#ifdef HAS_HALFOP +# ifdef IRC_UNREAL + send_cmd(whosends(chan->ci), "MODE %s -ahoq %s %s %s %s", + chan->name, user->nick, user->nick, user->nick, + user->nick); +# else + send_cmd(whosends(chan->ci), "MODE %s -ho %s %s", chan->name, + user->nick, user->nick); +# endif +#else + send_cmd(whosends(chan->ci), "MODE %s -o %s", chan->name, + user->nick); +#endif + return 0; + } + + return 1; +} + +/*************************************************************************/ + +/* Check whether a user should be opped on a channel, and if so, do it. + * Return 1 if the user was opped, 0 otherwise. (Updates the channel's + * last used time if the user was opped.) */ + +int check_should_op(User * user, const char *chan) +{ + ChannelInfo *ci = cs_findchan(chan); + + if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+') + return 0; + + if ((ci->flags & CI_SECURE) && !nick_identified(user)) + return 0; + + if (check_access(user, ci, CA_AUTOOP)) { + send_cmd(whosends(ci), "MODE %s +o %s", chan, user->nick); + return 1; + } + + return 0; +} + +/*************************************************************************/ + +/* Check whether a user should be voiced on a channel, and if so, do it. + * Return 1 if the user was voiced, 0 otherwise. */ + +int check_should_voice(User * user, const char *chan) +{ + ChannelInfo *ci = cs_findchan(chan); + + if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+') + return 0; + + if ((ci->flags & CI_SECURE) && !nick_identified(user)) + return 0; + + if (check_access(user, ci, CA_AUTOVOICE)) { + send_cmd(whosends(ci), "MODE %s +v %s", chan, user->nick); + return 1; + } + + return 0; +} + +/*************************************************************************/ + +#ifdef HAS_HALFOP + +int check_should_halfop(User * user, const char *chan) +{ + ChannelInfo *ci = cs_findchan(chan); + + if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+') + return 0; + + if (check_access(user, ci, CA_AUTOHALFOP)) { + send_cmd(whosends(ci), "MODE %s +h %s", chan, user->nick); + return 1; + } + + return 0; +} + +#endif + +/*************************************************************************/ + +#if defined(IRC_UNREAL) || defined(IRC_VIAGRA) + +int check_should_owner(User * user, const char *chan) +{ + ChannelInfo *ci = cs_findchan(chan); + + if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+') + return 0; + + if (((ci->flags & CI_SECUREFOUNDER) && is_real_founder(user, ci)) + || (!(ci->flags & CI_SECUREFOUNDER) && is_founder(user, ci))) { + send_cmd(whosends(ci), "MODE %s +oq %s %s", chan, user->nick, + user->nick); + return 1; + } + + return 0; +} + +#endif + +/*************************************************************************/ + +#if defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_ULTIMATE3) + +int check_should_protect(User * user, const char *chan) +{ + ChannelInfo *ci = cs_findchan(chan); + + if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+') + return 0; + + if (check_access(user, ci, CA_AUTOPROTECT)) { + send_cmd(whosends(ci), "MODE %s +oa %s %s", chan, user->nick, + user->nick); + return 1; + } + + return 0; +} + +#endif + +/*************************************************************************/ + +/* Tiny helper routine to get ChanServ out of a channel after it went in. */ + +static void timeout_leave(Timeout * to) +{ + char *chan = to->data; + ChannelInfo *ci = cs_findchan(chan); + + if (ci) /* Check cos the channel may be dropped in the meantime */ + ci->flags &= ~CI_INHABIT; + + 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, char *chan) +{ + ChannelInfo *ci = cs_findchan(chan); + Channel *c; + AutoKick *akick; + int i; + NickCore *nc; + char *av[3]; + char mask[BUFSIZE]; + const char *reason; + Timeout *t; + + if (!ci) + return 0; + + if (is_oper(user) || is_services_admin(user)) + return 0; + + if (ci->flags & CI_VERBOTEN) { + get_idealban(ci, user, mask, sizeof(mask)); + reason = + ci->forbidreason ? ci->forbidreason : getstring(user->na, + CHAN_MAY_NOT_BE_USED); + goto kick; + } + + if (ci->flags & CI_SUSPENDED) { + get_idealban(ci, user, mask, sizeof(mask)); + reason = + ci->forbidreason ? ci->forbidreason : getstring(user->na, + CHAN_MAY_NOT_BE_USED); + goto kick; + } + + if (nick_recognized(user)) + nc = user->na->nc; + else + nc = NULL; + +#if defined (IRC_ULTIMATE) || defined(IRC_ULTIMATE3) || defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_HYBRID) + /* + * Before we go through akick lists, see if they're excepted FIRST + * We cannot kick excempted users that are akicked or not on the channel access list + * as that will start services <-> server wars which ends up as a DoS against services. + * + * UltimateIRCd 3.x at least informs channel staff when a joining user is matching an exempt. + */ + if (is_excepted(ci, user) == 1) { + return 0; + } +#endif + + for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) { + if (!(akick->flags & AK_USED)) + continue; + if ((akick->flags & AK_ISNICK && akick->u.nc == nc) + || (!(akick->flags & AK_ISNICK) + && match_usermask(akick->u.mask, user))) { + if (debug >= 2) + alog("debug: %s matched akick %s", user->nick, + (akick->flags & AK_ISNICK) ? akick->u.nc-> + display : akick->u.mask); + if (akick->flags & AK_ISNICK) + get_idealban(ci, user, mask, sizeof(mask)); + else + strcpy(mask, akick->u.mask); + reason = akick->reason ? akick->reason : CSAutokickReason; + goto kick; + } + } + + if (check_access(user, ci, CA_NOJOIN)) { + get_idealban(ci, user, mask, sizeof(mask)); + reason = getstring(user->na, CHAN_NOT_ALLOWED_TO_JOIN); + goto kick; + } + + return 0; + + kick: + if (debug) + alog("debug: channel: AutoKicking %s!%s@%s from %s", user->nick, + user->username, GetHost(user), chan); + + /* Remember that the user has not been added to our channel user list + * yet, so we check whether the channel does not exist OR has no user + * on it (before SJOIN would have created the channel structure, while + * JOIN would not). */ + /* Don't check for CI_INHABIT before for the Channel record cos else + * c may be NULL even if it exists */ + if ((!(c = findchan(chan)) || c->usercount == 0) + && !(ci->flags & CI_INHABIT)) { +#if defined(IRC_BAHAMUT) + send_cmd(s_ChanServ, "SJOIN %lu %s", + (c ? c->creation_time : time(NULL)), chan); +#elif defined(IRC_HYBRID) + send_cmd(NULL, "SJOIN %ld %s + :@%s", + time(NULL), chan, s_ChanServ); +#else + send_cmd(s_ChanServ, "JOIN %s", chan); +#endif + t = add_timeout(CSInhabit, timeout_leave, 0); + t->data = sstrdup(chan); + ci->flags |= CI_INHABIT; + } + + if (c) { + av[0] = chan; + av[1] = sstrdup("+b"); + av[2] = mask; + do_cmode(whosends(ci), 3, av); + free(av[1]); + } + + send_cmd(whosends(ci), "MODE %s +b %s %lu", chan, mask, time(NULL)); + send_cmd(whosends(ci), "KICK %s %s :%s", chan, user->nick, reason); + + return 1; +} + +/*************************************************************************/ + +/* Record the current channel topic in the ChannelInfo structure. */ + +void record_topic(const char *chan) +{ + Channel *c; + ChannelInfo *ci; + + if (readonly) + return; + c = findchan(chan); + if (!c || !(ci = c->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(const char *chan) +{ + Channel *c = findchan(chan); + ChannelInfo *ci; + + if (!c || !(ci = c->ci) || !(ci->flags & CI_KEEPTOPIC)) + return; + if (c->topic) + free(c->topic); + if (ci->last_topic) { + c->topic = sstrdup(ci->last_topic); + strscpy(c->topic_setter, ci->last_topic_setter, NICKMAX); + c->topic_time = ci->last_topic_time; + } else { + c->topic = NULL; + strscpy(c->topic_setter, s_ChanServ, NICKMAX); + } +#ifdef IRC_HYBRID + if (whosends(ci) == s_ChanServ) { + send_cmd(NULL, "SJOIN %ld %s + :%s", time(NULL), chan, s_ChanServ); + send_cmd(NULL, "MODE %s +o %s", chan, s_ChanServ); + } + send_cmd(whosends(ci), "TOPIC %s :%s", chan, c->topic ? c->topic : ""); + if (whosends(ci) == s_ChanServ) { + send_cmd(s_ChanServ, "PART %s", chan); + } +#else + send_cmd(whosends(ci), "TOPIC %s %s %lu :%s", c->name, c->topic_setter, + c->topic_time, c->topic ? c->topic : ""); +#endif +} + +/*************************************************************************/ + +/* See if the topic is locked on the given channel, and return 1 (and fix + * the topic) if so. */ + +int check_topiclock(Channel * c, time_t topic_time) +{ + ChannelInfo *ci; + + if (!(ci = c->ci) || !(ci->flags & CI_TOPICLOCK)) + return 0; + + if (c->topic) + free(c->topic); + if (ci->last_topic) + c->topic = sstrdup(ci->last_topic); + else + c->topic = NULL; + + strscpy(c->topic_setter, ci->last_topic_setter, NICKMAX); +#ifdef IRC_UNREAL + /* Because older timestamps are rejected */ + c->topic_time = topic_time + 1; +#else + c->topic_time = ci->last_topic_time; +#endif + +#ifdef IRC_HYBRID + if (whosends(ci) == s_ChanServ) { + send_cmd(NULL, "SJOIN %ld %s + :%s", time(NULL), c->name, + s_ChanServ); + send_cmd(NULL, "MODE %s +o %s", c->name, s_ChanServ); + } + send_cmd(whosends(ci), "TOPIC %s :%s", c->name, + c->topic ? c->topic : ""); + if (whosends(ci) == s_ChanServ) { + send_cmd(s_ChanServ, "PART %s", c->name); + } +#else + send_cmd(whosends(ci), "TOPIC %s %s %lu :%s", c->name, c->topic_setter, + c->topic_time, c->topic ? c->topic : ""); +#endif + return 1; +} + +/*************************************************************************/ + +/* Remove all channels which have expired. */ + +void expire_chans() +{ + ChannelInfo *ci, *next; + int i; + time_t now = time(NULL); + + if (!CSExpire) + return; + + for (i = 0; i < 256; i++) { + for (ci = chanlists[i]; ci; ci = next) { + next = ci->next; + if (!ci->c && now - ci->last_used >= CSExpire + && !(ci->flags & (CI_VERBOTEN | CI_NO_EXPIRE))) { + alog("Expiring channel %s (founder: %s)", ci->name, + (ci->founder ? ci->founder->display : "(none)")); + delchan(ci); + } + } + } +} + +/*************************************************************************/ + +/* Remove a (deleted or expired) nickname from all channel lists. */ + +void cs_remove_nick(const NickCore * nc) +{ + int i, j; + ChannelInfo *ci, *next; + ChanAccess *ca; + AutoKick *akick; + + for (i = 0; i < 256; i++) { + for (ci = chanlists[i]; ci; ci = next) { + next = ci->next; + if (ci->founder == nc) { + if (ci->successor) { + NickCore *nc2 = ci->successor; + if (!nick_is_services_admin(nc2) && nc2->channelmax > 0 + && nc2->channelcount >= nc2->channelmax) { + alog("%s: Successor (%s) of %s owns too many channels, " "deleting channel", s_ChanServ, nc2->display, ci->name); + delchan(ci); + continue; + } else { + alog("%s: Transferring foundership of %s from deleted " "nick %s to successor %s", s_ChanServ, ci->name, nc->display, nc2->display); + ci->founder = nc2; + ci->successor = NULL; + nc2->channelcount++; +#ifdef USE_RDB + if (rdb_open()) { + rdb_cs_set_founder(ci->name, nc2->display); + rdb_close(); + } +#endif + } + } else { + alog("%s: Deleting channel %s owned by deleted nick %s", s_ChanServ, ci->name, nc->display); +#ifndef IRC_HYBRID + /* Maybe move this to delchan() ? */ + if ((ci->c) && (ci->c->mode & CMODE_r)) { + ci->c->mode &= ~CMODE_r; + send_cmd(whosends(ci), "MODE %s -r", ci->name); + } +#endif + + delchan(ci); + continue; + } + } + + if (ci->successor == nc) + ci->successor = NULL; + + for (ca = ci->access, j = ci->accesscount; j > 0; ca++, j--) { + if (ca->in_use && ca->nc == nc) { + ca->in_use = 0; + ca->nc = NULL; + } + } + + for (akick = ci->akick, j = ci->akickcount; j > 0; + akick++, j--) { + if ((akick->flags & AK_USED) && (akick->flags & AK_ISNICK) + && akick->u.nc == nc) { + if (akick->creator) { + free(akick->creator); + akick->creator = NULL; + } + if (akick->reason) { + free(akick->reason); + akick->reason = NULL; + } + akick->flags = 0; + akick->u.nc = NULL; + } + } + } + } +#ifdef USE_RDB + if (rdb_open()) { + rdb_cs_deluser(nc->display); + rdb_close(); + } +#endif +} + +/*************************************************************************/ + +/* Removes any reference to a bot */ + +void cs_remove_bot(const BotInfo * bi) +{ + int i; + ChannelInfo *ci; + + for (i = 0; i < 256; i++) + for (ci = chanlists[i]; ci; ci = ci->next) + if (ci->bi == bi) + ci->bi = NULL; +} + +/*************************************************************************/ + +/* 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[tolower(chan[1])]; ci; ci = ci->next) { + if (stricmp(ci->name, chan) == 0) + return ci; + } + return 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]; + + /* Resetting the last used time */ + if (level > 0) + ci->last_used = time(NULL); + + if (level == ACCESS_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 == ACCESS_INVALID) + return 0; + if (what == CA_AUTODEOP || what == CA_NOJOIN) + return level <= ci->levels[what]; + else + return level >= ci->levels[what]; +} + +/*************************************************************************/ +/*********************** ChanServ private routines ***********************/ +/*************************************************************************/ + +/* Insert a channel alphabetically into the database. */ + +static void alpha_insert_chan(ChannelInfo * ci) +{ + ChannelInfo *ptr, *prev; + char *chan = ci->name; + + for (prev = NULL, ptr = chanlists[tolower(chan[1])]; + ptr != NULL && stricmp(ptr->name, chan) < 0; + prev = ptr, ptr = ptr->next); + ci->prev = prev; + ci->next = ptr; + if (!prev) + chanlists[tolower(chan[1])] = 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) +{ + int i; + ChannelInfo *ci; + + ci = scalloc(sizeof(ChannelInfo), 1); + strscpy(ci->name, chan, CHANMAX); + ci->time_registered = time(NULL); + reset_levels(ci); + ci->ttb = scalloc(2 * TTB_SIZE, 1); + for (i = 0; i < TTB_SIZE; i++) + ci->ttb[i] = 0; + 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; + NickCore *nc = ci->founder; + + if (ci->bi) { + ci->bi->chancount--; + } + if (ci->c) { + if (ci->bi && ci->c->usercount >= BSMinUsers) { + send_cmd(ci->bi->nick, "PART %s", ci->c->name); + } + ci->c->ci = NULL; + } +#ifdef USE_RDB + if (rdb_open()) { + rdb_cs_delchan(ci); + rdb_close(); + } +#endif + if (ci->next) + ci->next->prev = ci->prev; + if (ci->prev) + ci->prev->next = ci->next; + else + chanlists[tolower(ci->name[1])] = ci->next; + if (ci->desc) + free(ci->desc); + if (ci->mlock_key) + free(ci->mlock_key); +#ifdef HAS_FMODE + if (ci->mlock_flood) + free(ci->mlock_flood); +#endif +#ifdef HAS_LMODE + if (ci->mlock_redirect) + free(ci->mlock_redirect); +#endif + if (ci->last_topic) + free(ci->last_topic); + if (ci->forbidby) + free(ci->forbidby); + if (ci->forbidreason) + free(ci->forbidreason); + if (ci->access) + free(ci->access); + for (i = 0; i < ci->akickcount; i++) { + if (!(ci->akick[i].flags & AK_ISNICK) && ci->akick[i].u.mask) + free(ci->akick[i].u.mask); + if (ci->akick[i].reason) + free(ci->akick[i].reason); + if (ci->akick[i].creator) + free(ci->akick[i].creator); + } + 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); + } + if (ci->ttb) + free(ci->ttb); + for (i = 0; i < ci->bwcount; i++) { + if (ci->badwords[i].word) + free(ci->badwords[i].word); + } + if (ci->badwords) + free(ci->badwords); + free(ci); + if (nc) + nc->channelcount--; + + return 1; +} + +/*************************************************************************/ + +/* 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), 1); + 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? */ + +int is_founder(User * user, ChannelInfo * ci) +{ + if (user->isSuperAdmin) { + return 1; + } + + if (user->na && user->na->nc == ci->founder) { + if ((nick_identified(user) + || (nick_recognized(user) && !(ci->flags & CI_SECURE)))) + return 1; + } + if (is_identified(user, ci)) + return 1; + return 0; +} + +/*************************************************************************/ + +static int is_real_founder(User * user, ChannelInfo * ci) +{ + if (user->isSuperAdmin) { + return 1; + } + + if (user->na && user->na->nc == ci->founder) { + if ((nick_identified(user) + || (nick_recognized(user) && !(ci->flags & CI_SECURE)))) + 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; +} + +/*************************************************************************/ + +/* Returns the ChanAccess entry for an user */ + +ChanAccess *get_access_entry(NickCore * nc, ChannelInfo * ci) +{ + ChanAccess *access; + int i; + + for (access = ci->access, i = 0; i < ci->accesscount; access++, i++) + if (access->in_use && access->nc == nc) + return access; + + return NULL; +} + +/*************************************************************************/ + +/* 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. */ + +int get_access(User * user, ChannelInfo * ci) +{ + ChanAccess *access; + + if (!ci) + return 0; + + if (is_founder(user, ci)) + return ACCESS_FOUNDER; + + if (!user->na) + return 0; + + if (nick_identified(user) + || (nick_recognized(user) && !(ci->flags & CI_SECURE))) + if ((access = get_access_entry(user->na->nc, ci))) + return access->level; + + return 0; +} + +/*************************************************************************/ + +void update_cs_lastseen(User * user, ChannelInfo * ci) +{ + ChanAccess *access; + + if (!ci || !user->na) + return; + + if (is_founder(user, ci) || nick_identified(user) + || (nick_recognized(user) && !(ci->flags & CI_SECURE))) + if ((access = get_access_entry(user->na->nc, ci))) + access->last_seen = time(NULL); +} + +/*************************************************************************/ + +/* Returns the best ban possible for an user depending of the bantype + value. */ + +int get_idealban(ChannelInfo * ci, User * u, char *ret, int retlen) +{ + char *mask; + + if (!ci || !u || !ret || retlen == 0) + return 0; + + switch (ci->bantype) { + case 0: + snprintf(ret, retlen, "*!%s@%s", GetIdent(u), GetHost(u)); + return 1; + case 1: + snprintf(ret, retlen, "*!%s%s@%s", + (strlen(GetIdent(u)) < + (*(GetIdent(u)) == + '~' ? USERMAX + 1 : USERMAX) ? "*" : ""), + (*(GetIdent(u)) == '~' ? GetIdent(u) + 1 : GetIdent(u)), + GetHost(u)); + return 1; + case 2: + snprintf(ret, retlen, "*!*@%s", GetHost(u)); + return 1; + case 3: + mask = create_mask(u); + snprintf(ret, retlen, "*!%s", mask); + free(mask); + return 1; + + default: + return 0; + } +} + +/*************************************************************************/ + +static void make_unidentified(User * u, ChannelInfo * ci) +{ + struct u_chaninfolist *uci; + + if (!u || !ci) + return; + + for (uci = u->founder_chans; uci; uci = uci->next) { + if (uci->chan == ci) { + if (uci->next) + uci->next->prev = uci->prev; + if (uci->prev) + uci->prev->next = uci->next; + else + u->founder_chans = uci->next; + free(uci); + break; + } + } +} + +/*************************************************************************/ + +#ifdef HAS_FMODE + +char *cs_get_flood(ChannelInfo * ci) +{ + return ci->mlock_flood; +} + +#endif + +/*************************************************************************/ + +char *cs_get_key(ChannelInfo * ci) +{ + return ci->mlock_key; +} + +/*************************************************************************/ + +char *cs_get_limit(ChannelInfo * ci) +{ + static char limit[16]; + + if (ci->mlock_limit == 0) + return NULL; + + snprintf(limit, sizeof(limit), "%lu", ci->mlock_limit); + return limit; +} + +/*************************************************************************/ + +#ifdef HAS_LMODE + +char *cs_get_redirect(ChannelInfo * ci) +{ + return ci->mlock_redirect; +} + +#endif + +/*************************************************************************/ + +#ifdef HAS_FMODE + +void cs_set_flood(ChannelInfo * ci, char *value) +{ + char *dp, *end; + + if (ci->mlock_flood) + free(ci->mlock_flood); + + /* This looks ugly, but it works ;) */ + if (value && *value != ':' + && (strtoul((*value == '*' ? value + 1 : value), &dp, 10) > 0) + && (*dp == ':') && (*(++dp) != 0) && (strtoul(dp, &end, 10) > 0) + && (*end == 0)) { + ci->mlock_flood = sstrdup(value); + } else { + ci->mlock_on &= ~CMODE_f; + ci->mlock_flood = NULL; + } +} + +#endif + +/*************************************************************************/ + +void cs_set_key(ChannelInfo * ci, char *value) +{ + if (ci->mlock_key) + free(ci->mlock_key); + + /* Don't allow keys with a coma */ + if (value && *value != ':' && !strchr(value, ',')) { + ci->mlock_key = sstrdup(value); + } else { + ci->mlock_on &= ~CMODE_k; + ci->mlock_key = NULL; + } +} + +/*************************************************************************/ + +void cs_set_limit(ChannelInfo * ci, char *value) +{ + ci->mlock_limit = value ? strtoul(value, NULL, 10) : 0; + + if (ci->mlock_limit <= 0) + ci->mlock_on &= ~CMODE_l; +} + +/*************************************************************************/ + +#ifdef HAS_LMODE + +void cs_set_redirect(ChannelInfo * ci, char *value) +{ + if (ci->mlock_redirect) + free(ci->mlock_redirect); + + /* Don't allow keys with a coma */ + if (value && *value == '#') { + ci->mlock_redirect = sstrdup(value); + } else { + ci->mlock_on &= ~CMODE_L; + ci->mlock_redirect = NULL; + } +} + +#endif + +int get_access_level(ChannelInfo * ci, NickAlias * na) +{ + ChanAccess *access; + int num; + + if (na->nc == ci->founder) { + return ACCESS_FOUNDER; + } + + for (num = 0; num < ci->accesscount; num++) { + + access = &ci->access[num]; + + if (!access->in_use) + return 0; + + if (access->nc == na->nc) { + return access->level; + } + + } + + return 0; + +} + +char *get_xop_level(int level) +{ + + if (level < ACCESS_VOP) { + return "Err"; +#ifdef HAS_HALFOP + } else if (level < ACCESS_HOP) { + return "VOP"; + } else if (level < ACCESS_AOP) { + return "HOP"; +#else + } else if (level < ACCESS_AOP) { + return "VOP"; +#endif + } else if (level < ACCESS_SOP) { + return "AOP"; + } else if (level < ACCESS_FOUNDER) { + return "SOP"; + } else { + return "Founder"; + } + +} + +/*************************************************************************/ +/*********************** ChanServ command routines ***********************/ +/*************************************************************************/ + +static int do_help(User * u) +{ + char *cmd = strtok(NULL, ""); + + if (!cmd) { + notice_help(s_ChanServ, u, CHAN_HELP); +#ifdef IRC_UNREAL + notice_help(s_ChanServ, u, CHAN_HELP_UNREAL); +#endif +#ifdef IRC_VIAGRA + notice_help(s_ChanServ, u, CHAN_HELP_UNREAL); +#endif +#ifdef IRC_ULTIMATE + notice_help(s_ChanServ, u, CHAN_HELP_ULTIMATE); +#endif +#ifdef IRC_ULTIMATE3 + notice_help(s_ChanServ, u, CHAN_HELP_ULTIMATE3); +#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); + moduleDisplayHelp(2, u); + } 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->na, levelinfo[i].desc)); + } + } else { + mod_help_cmd(s_ChanServ, u, CHANSERV, cmd); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_register(User * u) +{ + char *chan = strtok(NULL, " "); + char *pass = strtok(NULL, " "); + char *desc = strtok(NULL, ""); + NickCore *nc; + Channel *c; + ChannelInfo *ci; + struct u_chaninfolist *uc; + int is_servadmin = is_services_admin(u); +#ifdef USE_ENCRYPTION + char founderpass[PASSMAX + 1]; +#endif + + if (readonly) { + notice_lang(s_ChanServ, u, CHAN_REGISTER_DISABLED); + return MOD_CONT; + } + + if (checkDefCon(DEFCON_NO_NEW_CHANNELS)) { + notice_lang(s_ChanServ, u, OPER_DEFCON_DENIED); + return MOD_CONT; + } + + 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 (!u->na || !(nc = u->na->nc)) { + 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) { + alog("%s: Attempt to register FORBIDden channel %s by %s!%s@%s", s_ChanServ, ci->name, u->nick, u->username, GetHost(u)); + notice_lang(s_ChanServ, u, CHAN_MAY_NOT_BE_REGISTERED, chan); + } else { + notice_lang(s_ChanServ, u, CHAN_ALREADY_REGISTERED, chan); + } + } else if (!stricmp(chan, "#")) { + notice_lang(s_ChanServ, u, CHAN_MAY_NOT_BE_REGISTERED, chan); + } else if (!(c = findchan(chan)) + || !chan_has_user_status(c, u, CUS_OP)) { + notice_lang(s_ChanServ, u, CHAN_MUST_BE_CHANOP); + + } else if (!is_servadmin && nc->channelmax > 0 + && nc->channelcount >= nc->channelmax) { + notice_lang(s_ChanServ, u, + nc->channelcount > + nc-> + channelmax ? CHAN_EXCEEDED_CHANNEL_LIMIT : + CHAN_REACHED_CHANNEL_LIMIT, nc->channelmax); + + } else if (!(ci = makechan(chan))) { + alog("%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) { + alog("%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->bantype = CSDefBantype; + ci->flags = CSDefFlags; +#ifdef IRC_HYBRID + ci->mlock_on = CMODE_n | CMODE_t; +#else + ci->mlock_on = CMODE_n | CMODE_t | CMODE_r; +#endif + ci->memos.memomax = MSMaxMemos; + ci->last_used = ci->time_registered; + ci->founder = nc; +#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; + } + ci->bi = NULL; + ci->botflags = BSDefFlags; + ci->founder->channelcount++; + alog("%s: Channel '%s' registered by %s!%s@%s", s_ChanServ, chan, + u->nick, u->username, GetHost(u)); + 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 = scalloc(sizeof(*uc), 1); + 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(c); +#ifdef IRC_ULTIMATE3 + send_cmd(s_ChanServ, "MODE %s +a %s", chan, u->nick); +#endif + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int 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 (!nick_identified(u)) { + notice_lang(s_ChanServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ); + } else if (is_founder(u, ci)) { + notice_lang(s_ChanServ, u, NICK_ALREADY_IDENTIFIED); + } else { + int res; + + if ((res = check_password(pass, ci->founderpass)) == 1) { + if (!is_identified(u, ci)) { + uc = scalloc(sizeof(*uc), 1); + uc->next = u->founder_chans; + if (u->founder_chans) + u->founder_chans->prev = uc; + u->founder_chans = uc; + uc->chan = ci; + alog("%s: %s!%s@%s identified for %s", s_ChanServ, u->nick, + u->username, GetHost(u), ci->name); + } + + notice_lang(s_ChanServ, u, CHAN_IDENTIFY_SUCCEEDED, chan); + } else if (res < 0) { + alog("%s: check_password failed for %s", s_ChanServ, ci->name); + notice_lang(s_ChanServ, u, CHAN_IDENTIFY_FAILED); + } else { + alog("%s: Failed IDENTIFY for %s by %s!%s@%s", + s_ChanServ, ci->name, u->nick, u->username, GetHost(u)); + notice_lang(s_ChanServ, u, PASSWORD_INCORRECT); + bad_password(u); + } + + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_logout(User * u) +{ + char *chan = strtok(NULL, " "); + char *nick = strtok(NULL, " "); + ChannelInfo *ci; + User *u2 = NULL; + int is_servadmin = is_services_admin(u); + + if (!chan || (!nick && !is_servadmin)) { + syntax_error(s_ChanServ, u, "LOGOUT", + (!is_servadmin ? CHAN_LOGOUT_SYNTAX : + CHAN_LOGOUT_SERVADMIN_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 (nick && !(u2 = finduser(nick))) { + notice_lang(s_ChanServ, u, NICK_X_NOT_IN_USE, nick); + } else if (!is_servadmin && u2 != u && !is_real_founder(u, ci)) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else { + if (u2) { + make_unidentified(u2, ci); + notice_lang(s_ChanServ, u, CHAN_LOGOUT_SUCCEEDED, nick, chan); + } else { + int i; + for (i = 0; i < 1024; i++) + for (u2 = userlist[i]; u2; u2 = u2->next) + make_unidentified(u2, ci); + notice_lang(s_ChanServ, u, CHAN_LOGOUT_ALL_SUCCEEDED, chan); + } + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_drop(User * u) +{ + char *chan = strtok(NULL, " "); + ChannelInfo *ci; + int is_servadmin = is_services_admin(u); + + if (readonly && !is_servadmin) { + notice_lang(s_ChanServ, u, CHAN_DROP_DISABLED); + return MOD_CONT; + } + + 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 && (ci->flags & CI_SUSPENDED)) { + notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan); + } else if (!is_servadmin + && (ci-> + flags & CI_SECUREFOUNDER ? !is_real_founder(u, + ci) : + !is_founder(u, ci))) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else { + int level = get_access(u, ci); + + if (readonly) /* in this case we know they're a Services admin */ + notice_lang(s_ChanServ, u, READ_ONLY_MODE); +#ifndef IRC_HYBRID + if (ci->c) { + ci->c->mode &= ~CMODE_r; + send_cmd(whosends(ci), "MODE %s -r", ci->name); + } +#endif + alog("%s: Channel %s dropped by %s!%s@%s (founder: %s)", + s_ChanServ, ci->name, u->nick, u->username, GetHost(u), + (ci->founder ? ci->founder->display : "(none)")); + + delchan(ci); + + /* We must make sure that the Services admin has not normally the right to + * drop the channel before issuing the wallops. + */ + if (WallDrop && is_servadmin && level < ACCESS_FOUNDER) + wallops(s_ChanServ, "\2%s\2 used DROP on channel \2%s\2", + u->nick, chan); + + notice_lang(s_ChanServ, u, CHAN_DROPPED, chan); + } + return MOD_CONT; +} + +/*************************************************************************/ + +/* 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). + */ +static int 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 MOD_CONT; + } + + if (cmd) { + if (stricmp(cmd, "DESC") == 0 || stricmp(cmd, "ENTRYMSG") == 0) + param = strtok(NULL, ""); + else + param = strtok(NULL, " "); + } else { + param = NULL; + } + + if (!param && (!cmd || (stricmp(cmd, "SUCCESSOR") != 0 && + stricmp(cmd, "URL") != 0 && + stricmp(cmd, "EMAIL") != 0 && + stricmp(cmd, "ENTRYMSG") != 0))) { + 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 + && (ci-> + flags & CI_SECUREFOUNDER ? !is_real_founder(u, + ci) : + !is_founder(u, ci))) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else { + do_set_founder(u, ci, param); + } + } else if (stricmp(cmd, "SUCCESSOR") == 0) { + if (!is_servadmin + && (ci-> + flags & CI_SECUREFOUNDER ? !is_real_founder(u, + ci) : + !is_founder(u, ci))) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else { + do_set_successor(u, ci, param); + } + } else if (stricmp(cmd, "PASSWORD") == 0) { + if (!is_servadmin + && (ci-> + flags & CI_SECUREFOUNDER ? !is_real_founder(u, + ci) : + !is_founder(u, ci))) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } 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) { + notice_lang(s_ChanServ, u, OBSOLETE_COMMAND, "TOPIC"); + } else if (stricmp(cmd, "BANTYPE") == 0) { + do_set_bantype(u, ci, param); + } else if (stricmp(cmd, "MLOCK") == 0) { + do_set_mlock(u, ci, param); + } else if (stricmp(cmd, "KEEPTOPIC") == 0) { + do_set_keeptopic(u, ci, param); + } else if (stricmp(cmd, "TOPICLOCK") == 0) { + do_set_topiclock(u, ci, param); + } else if (stricmp(cmd, "PRIVATE") == 0) { + do_set_private(u, ci, param); + } else if (stricmp(cmd, "SECUREOPS") == 0) { + do_set_secureops(u, ci, param); + } else if (stricmp(cmd, "SECUREFOUNDER") == 0) { + if (!is_servadmin + && (ci-> + flags & CI_SECUREFOUNDER ? !is_real_founder(u, + ci) : + !is_founder(u, ci))) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else { + do_set_securefounder(u, ci, param); + } + } else if (stricmp(cmd, "RESTRICTED") == 0) { + do_set_restricted(u, ci, param); + } else if (stricmp(cmd, "SECURE") == 0) { + do_set_secure(u, ci, param); + } else if (stricmp(cmd, "SIGNKICK") == 0) { + do_set_signkick(u, ci, param); + } else if (stricmp(cmd, "OPNOTICE") == 0) { + do_set_opnotice(u, ci, param); + } else if (stricmp(cmd, "XOP") == 0) { + do_set_xop(u, ci, param); + } else if (stricmp(cmd, "PEACE") == 0) { + do_set_peace(u, ci, param); + } else if (stricmp(cmd, "NOEXPIRE") == 0) { + do_set_noexpire(u, ci, param); + } else { + notice_lang(s_ChanServ, u, CHAN_SET_UNKNOWN_OPTION, cmd); + notice_lang(s_ChanServ, u, MORE_INFO, s_ChanServ, "SET"); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_founder(User * u, ChannelInfo * ci, char *param) +{ + NickAlias *na = findnick(param); + NickCore *nc, *nc0 = ci->founder; + + if (!na) { + notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, param); + return MOD_CONT; + } else if (na->status & NS_VERBOTEN) { + notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, param); + return MOD_CONT; + } + + nc = na->nc; + if (nc->channelmax > 0 && nc->channelcount >= nc->channelmax + && !is_services_admin(u)) { + notice_lang(s_ChanServ, u, CHAN_SET_FOUNDER_TOO_MANY_CHANS, param); + return MOD_CONT; + } + + alog("%s: Changing founder of %s from %s to %s by %s!%s@%s", + s_ChanServ, ci->name, ci->founder->display, nc->display, u->nick, + u->username, GetHost(u)); + + /* Founder and successor must not be the same group */ + if (nc == ci->successor) + ci->successor = NULL; + + nc0->channelcount--; + ci->founder = nc; + nc->channelcount++; + + notice_lang(s_ChanServ, u, CHAN_FOUNDER_CHANGED, ci->name, param); + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_successor(User * u, ChannelInfo * ci, char *param) +{ + NickAlias *na; + NickCore *nc; + + if (param) { + na = findnick(param); + + if (!na) { + notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, param); + return MOD_CONT; + } + if (na->status & NS_VERBOTEN) { + notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, param); + return MOD_CONT; + } + if (na->nc == ci->founder) { + notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_IS_FOUNDER, param, + ci->name); + return MOD_CONT; + } + nc = na->nc; + + } else { + nc = NULL; + } + + alog("%s: Changing successor of %s from %s to %s by %s!%s@%s", + s_ChanServ, ci->name, + (ci->successor ? ci->successor->display : "none"), + (nc ? nc->display : "none"), u->nick, u->username, GetHost(u)); + + ci->successor = nc; + + if (nc) + notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_CHANGED, ci->name, + param); + else + notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_UNSET, ci->name); + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_password(User * u, ChannelInfo * ci, char *param) +{ +#ifdef USE_ENCRYPTION + int len = strlen(param); + + if (len > PASSMAX) { + len = PASSMAX; + param[len] = 0; + notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, PASSMAX); + } + + if (encrypt(param, len, ci->founderpass, PASSMAX) < 0) { + memset(param, 0, strlen(param)); + alog("%s: Failed to encrypt password for %s (set)", s_ChanServ, + ci->name); + notice_lang(s_ChanServ, u, CHAN_SET_PASSWORD_FAILED); + return MOD_CONT; + } + + memset(param, 0, strlen(param)); + 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) < ACCESS_FOUNDER) { + alog("%s: %s!%s@%s set password as Services admin for %s", + s_ChanServ, u->nick, u->username, GetHost(u), ci->name); + if (WallSetpass) + wallops(s_ChanServ, + "\2%s\2 set password as Services admin for channel \2%s\2", + u->nick, ci->name); + } else { + alog("%s: %s!%s@%s changed password of %s (founder: %s)", + s_ChanServ, u->nick, u->username, GetHost(u), + ci->name, ci->founder->display); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int 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); + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_url(User * u, ChannelInfo * ci, char *param) +{ + if (ci->url) + free(ci->url); + if (param) { + 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); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_email(User * u, ChannelInfo * ci, char *param) +{ + if (ci->email) + free(ci->email); + if (param) { + 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); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int 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); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_mlock(User * u, ChannelInfo * ci, char *param) +{ + int add = -1; /* 1 if adding, 0 if deleting, -1 if neither */ + unsigned char mode; + CBMode *cbm; + + if (checkDefCon(DEFCON_NO_MLOCK_CHANGE)) { + notice_lang(s_ChanServ, u, OPER_DEFCON_DENIED); + return MOD_CONT; + } + + /* Reinitialize everything */ +#ifdef IRC_HYBRID + ci->mlock_on = 0; +#else + ci->mlock_on = CMODE_r; +#endif + ci->mlock_off = ci->mlock_limit = 0; + ci->mlock_key = NULL; +#ifdef HAS_FMODE + ci->mlock_flood = NULL; +#endif +#ifdef HAS_LMODE + ci->mlock_redirect = NULL; +#endif + + while ((mode = *param++)) { + switch (mode) { + case '+': + add = 1; + continue; + case '-': + add = 0; + continue; + default: + if (add < 0) + continue; + } + + if ((int) mode < 128 && (cbm = &cbmodes[(int) mode])->flag != 0) { + if ((cbm->flags & CBM_NO_MLOCK) + || ((cbm->flags & CBM_NO_USER_MLOCK) && !is_oper(u))) { + notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_IMPOSSIBLE_CHAR, + mode); + } else if (add) { + ci->mlock_on |= cbm->flag; + ci->mlock_off &= ~cbm->flag; + if (cbm->cssetvalue) + cbm->cssetvalue(ci, strtok(NULL, " ")); + } else { + ci->mlock_off |= cbm->flag; + if (ci->mlock_on & cbm->flag) { + ci->mlock_on &= ~cbm->flag; + if (cbm->cssetvalue) + cbm->cssetvalue(ci, NULL); + } + } + } else { + notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_UNKNOWN_CHAR, mode); + } + } /* while (*param) */ + +#ifdef HAS_LMODE + /* We can't mlock +L if +l is not mlocked as well. */ + if ((ci->mlock_on & CMODE_L) && !(ci->mlock_on & CMODE_l)) { + ci->mlock_on &= ~CMODE_L; + free(ci->mlock_redirect); + notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_L_REQUIRED); + } +#endif + +#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) + /* We can't mlock +K if +i is not mlocked as well. */ + if ((ci->mlock_on & CMODE_K) && !(ci->mlock_on & CMODE_i)) { + ci->mlock_on &= ~CMODE_K; + notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_K_REQUIRED); + } +#endif + + /* Since we always enforce mode r there is no way to have no + * mode lock at all. + */ +#if defined(IRC_HYBRID) + /* James: Hybrid doesn't HAVE mode r, so now you have to check :P */ + if (get_mlock_modes(ci, 0)) +#endif + notice_lang(s_ChanServ, u, CHAN_MLOCK_CHANGED, ci->name, + get_mlock_modes(ci, 0)); + + + /* Implement the new lock. */ + if (ci->c) + check_modes(ci->c); + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_bantype(User * u, ChannelInfo * ci, char *param) +{ + char *endptr; + + int16 bantype = strtol(param, &endptr, 10); + + if (*endptr != 0 || bantype < 0 || bantype > 3) { + notice_lang(s_ChanServ, u, CHAN_SET_BANTYPE_INVALID, param); + } else { + ci->bantype = bantype; + notice_lang(s_ChanServ, u, CHAN_SET_BANTYPE_CHANGED, ci->name, + ci->bantype); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_keeptopic(User * u, ChannelInfo * ci, char *param) +{ + if (stricmp(param, "ON") == 0) { + ci->flags |= CI_KEEPTOPIC; + notice_lang(s_ChanServ, u, CHAN_SET_KEEPTOPIC_ON); + } else if (stricmp(param, "OFF") == 0) { + ci->flags &= ~CI_KEEPTOPIC; + notice_lang(s_ChanServ, u, CHAN_SET_KEEPTOPIC_OFF); + } else { + syntax_error(s_ChanServ, u, "SET KEEPTOPIC", + CHAN_SET_KEEPTOPIC_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_topiclock(User * u, ChannelInfo * ci, char *param) +{ + if (stricmp(param, "ON") == 0) { + ci->flags |= CI_TOPICLOCK; + notice_lang(s_ChanServ, u, CHAN_SET_TOPICLOCK_ON); + } else if (stricmp(param, "OFF") == 0) { + ci->flags &= ~CI_TOPICLOCK; + notice_lang(s_ChanServ, u, CHAN_SET_TOPICLOCK_OFF); + } else { + syntax_error(s_ChanServ, u, "SET TOPICLOCK", + CHAN_SET_TOPICLOCK_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_private(User * u, ChannelInfo * ci, char *param) +{ + if (stricmp(param, "ON") == 0) { + ci->flags |= CI_PRIVATE; + notice_lang(s_ChanServ, u, CHAN_SET_PRIVATE_ON); + } else if (stricmp(param, "OFF") == 0) { + ci->flags &= ~CI_PRIVATE; + notice_lang(s_ChanServ, u, CHAN_SET_PRIVATE_OFF); + } else { + syntax_error(s_ChanServ, u, "SET PRIVATE", + CHAN_SET_PRIVATE_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_secureops(User * u, ChannelInfo * ci, char *param) +{ + if (stricmp(param, "ON") == 0) { + ci->flags |= CI_SECUREOPS; + notice_lang(s_ChanServ, u, CHAN_SET_SECUREOPS_ON); + } else if (stricmp(param, "OFF") == 0) { + ci->flags &= ~CI_SECUREOPS; + notice_lang(s_ChanServ, u, CHAN_SET_SECUREOPS_OFF); + } else { + syntax_error(s_ChanServ, u, "SET SECUREOPS", + CHAN_SET_SECUREOPS_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_securefounder(User * u, ChannelInfo * ci, char *param) +{ + if (stricmp(param, "ON") == 0) { + ci->flags |= CI_SECUREFOUNDER; + notice_lang(s_ChanServ, u, CHAN_SET_SECUREFOUNDER_ON); + } else if (stricmp(param, "OFF") == 0) { + ci->flags &= ~CI_SECUREFOUNDER; + notice_lang(s_ChanServ, u, CHAN_SET_SECUREFOUNDER_OFF); + } else { + syntax_error(s_ChanServ, u, "SET SECUREFOUNDER", + CHAN_SET_SECUREFOUNDER_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_restricted(User * u, ChannelInfo * ci, char *param) +{ + if (stricmp(param, "ON") == 0) { + ci->flags |= CI_RESTRICTED; + if (ci->levels[CA_NOJOIN] < 0) + ci->levels[CA_NOJOIN] = 0; + notice_lang(s_ChanServ, u, CHAN_SET_RESTRICTED_ON); + } else if (stricmp(param, "OFF") == 0) { + ci->flags &= ~CI_RESTRICTED; + if (ci->levels[CA_NOJOIN] >= 0) + ci->levels[CA_NOJOIN] = -2; + notice_lang(s_ChanServ, u, CHAN_SET_RESTRICTED_OFF); + } else { + syntax_error(s_ChanServ, u, "SET RESTRICTED", + CHAN_SET_RESTRICTED_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_secure(User * u, ChannelInfo * ci, char *param) +{ + if (stricmp(param, "ON") == 0) { + ci->flags |= CI_SECURE; + notice_lang(s_ChanServ, u, CHAN_SET_SECURE_ON); + } else if (stricmp(param, "OFF") == 0) { + ci->flags &= ~CI_SECURE; + notice_lang(s_ChanServ, u, CHAN_SET_SECURE_OFF); + } else { + syntax_error(s_ChanServ, u, "SET SECURE", CHAN_SET_SECURE_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_signkick(User * u, ChannelInfo * ci, char *param) +{ + if (stricmp(param, "ON") == 0) { + ci->flags |= CI_SIGNKICK; + ci->flags &= ~CI_SIGNKICK_LEVEL; + notice_lang(s_ChanServ, u, CHAN_SET_SIGNKICK_ON); + } else if (stricmp(param, "LEVEL") == 0) { + ci->flags |= CI_SIGNKICK_LEVEL; + ci->flags &= ~CI_SIGNKICK; + notice_lang(s_ChanServ, u, CHAN_SET_SIGNKICK_LEVEL); + } else if (stricmp(param, "OFF") == 0) { + ci->flags &= ~(CI_SIGNKICK | CI_SIGNKICK_LEVEL); + notice_lang(s_ChanServ, u, CHAN_SET_SIGNKICK_OFF); + } else { + syntax_error(s_ChanServ, u, "SET SIGNKICK", + CHAN_SET_SIGNKICK_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_opnotice(User * u, ChannelInfo * ci, char *param) +{ + if (stricmp(param, "ON") == 0) { + ci->flags |= CI_OPNOTICE; + notice_lang(s_ChanServ, u, CHAN_SET_OPNOTICE_ON); + } else if (stricmp(param, "OFF") == 0) { + ci->flags &= ~CI_OPNOTICE; + notice_lang(s_ChanServ, u, CHAN_SET_OPNOTICE_OFF); + } else { + syntax_error(s_ChanServ, u, "SET OPNOTICE", + CHAN_SET_OPNOTICE_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +#define CHECKLEV(lev) ((ci->levels[(lev)] != ACCESS_INVALID) && (access->level >= ci->levels[(lev)])) + +static int do_set_xop(User * u, ChannelInfo * ci, char *param) +{ + if (stricmp(param, "ON") == 0) { + if (!(ci->flags & CI_XOP)) { + int i; + ChanAccess *access; + + for (access = ci->access, i = 0; i < ci->accesscount; + access++, i++) { + if (!access->in_use) + continue; + /* This will probably cause wrong levels to be set, but hey, + * it's better than losing it altogether. + */ + if (CHECKLEV(CA_AKICK) || CHECKLEV(CA_SET)) { + access->level = ACCESS_SOP; + } else if (CHECKLEV(CA_AUTOOP) || CHECKLEV(CA_OPDEOP) + || CHECKLEV(CA_OPDEOPME)) { + access->level = ACCESS_AOP; +#ifdef HAS_HALFOP + } else if (CHECKLEV(CA_AUTOHALFOP) || CHECKLEV(CA_HALFOP) + || CHECKLEV(CA_HALFOPME)) { + access->level = ACCESS_HOP; +#endif + } else if (CHECKLEV(CA_AUTOVOICE) || CHECKLEV(CA_VOICE) + || CHECKLEV(CA_VOICEME)) { + access->level = ACCESS_VOP; + } else { + access->in_use = 0; + access->nc = NULL; + } + } + + reset_levels(ci); + ci->flags |= CI_XOP; + } + + alog("%s: %s!%s@%s enabled XOP for %s", s_ChanServ, u->nick, + u->username, GetHost(u), ci->name); + notice_lang(s_ChanServ, u, CHAN_SET_XOP_ON); + } else if (stricmp(param, "OFF") == 0) { + ci->flags &= ~CI_XOP; + + alog("%s: %s!%s@%s disabled XOP for %s", s_ChanServ, u->nick, + u->username, GetHost(u), ci->name); + notice_lang(s_ChanServ, u, CHAN_SET_XOP_OFF); + } else { + syntax_error(s_ChanServ, u, "SET XOP", CHAN_SET_XOP_SYNTAX); + } + return MOD_CONT; +} + +#undef CHECKLEV + +/*************************************************************************/ + +static int do_set_peace(User * u, ChannelInfo * ci, char *param) +{ + if (stricmp(param, "ON") == 0) { + ci->flags |= CI_PEACE; + notice_lang(s_ChanServ, u, CHAN_SET_PEACE_ON); + } else if (stricmp(param, "OFF") == 0) { + ci->flags &= ~CI_PEACE; + notice_lang(s_ChanServ, u, CHAN_SET_PEACE_OFF); + } else { + syntax_error(s_ChanServ, u, "SET PEACE", CHAN_SET_PEACE_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_set_noexpire(User * u, ChannelInfo * ci, char *param) +{ + if (!is_services_admin(u)) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + return MOD_CONT; + } + if (stricmp(param, "ON") == 0) { + ci->flags |= CI_NO_EXPIRE; + notice_lang(s_ChanServ, u, CHAN_SET_NOEXPIRE_ON, ci->name); + } else if (stricmp(param, "OFF") == 0) { + ci->flags &= ~CI_NO_EXPIRE; + notice_lang(s_ChanServ, u, CHAN_SET_NOEXPIRE_OFF, ci->name); + } else { + syntax_error(s_ChanServ, u, "SET NOEXPIRE", + CHAN_SET_NOEXPIRE_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +/* `last' is set to the last index this routine was called with + * `perm' is incremented whenever a permission-denied error occurs + */ + +static int xop_del(User * u, ChanAccess * access, int *perm, int uacc, + int xlev) +{ + if (!access->in_use || access->level != xlev) + return 0; + if (!is_services_admin(u) && uacc <= access->level) { + (*perm)++; + return 0; + } + access->nc = NULL; + access->in_use = 0; + return 1; +} + +static int xop_del_callback(User * u, int num, va_list args) +{ + ChannelInfo *ci = va_arg(args, ChannelInfo *); + int *last = va_arg(args, int *); + int *perm = va_arg(args, int *); + int uacc = va_arg(args, int); + int xlev = va_arg(args, int); + + if (num < 1 || num > ci->accesscount) + return 0; + *last = num; + + return xop_del(u, &ci->access[num - 1], perm, uacc, xlev); +} + + +static int xop_list(User * u, int index, ChannelInfo * ci, + int *sent_header, int xlev, int xmsg) +{ + ChanAccess *access = &ci->access[index]; + + if (!access->in_use || access->level != xlev) + return 0; + + if (!*sent_header) { + notice_lang(s_ChanServ, u, xmsg, ci->name); + *sent_header = 1; + } + + notice_lang(s_ChanServ, u, CHAN_XOP_LIST_FORMAT, index + 1, + access->nc->display); + 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 xlev = va_arg(args, int); + int xmsg = va_arg(args, int); + + if (num < 1 || num > ci->accesscount) + return 0; + + return xop_list(u, num - 1, ci, sent_header, xlev, xmsg); +} + + +static int do_xop(User * u, char *xname, int xlev, int *xmsgs) +{ + char *chan = strtok(NULL, " "); + char *cmd = strtok(NULL, " "); + char *nick = strtok(NULL, " "); + + ChannelInfo *ci; + NickAlias *na; + NickCore *nc; + + int i; + int change = 0; + short ulev; + int is_list = (cmd && stricmp(cmd, "LIST") == 0); + int is_servadmin = is_services_admin(u); + ChanAccess *access; + + /* If CLEAR, we don't need any parameters. + * If LIST, we don't *require* any parameters, but we can take any. + * If DEL or ADD we require a nick. */ + if (!cmd || ((is_list || !stricmp(cmd, "CLEAR")) ? 0 : !nick)) { + syntax_error(s_ChanServ, u, xname, xmsgs[0]); + } 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->flags & CI_XOP)) { + notice_lang(s_ChanServ, u, CHAN_XOP_ACCESS, s_ChanServ); + } else if (stricmp(cmd, "ADD") == 0) { + if (readonly) { + notice_lang(s_ChanServ, u, xmsgs[1]); + return MOD_CONT; + } + + ulev = get_access(u, ci); + + if (!is_servadmin && (xlev >= ulev || ulev < ACCESS_AOP)) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + return MOD_CONT; + } + + na = findnick(nick); + if (!na) { + notice_lang(s_ChanServ, u, xmsgs[2]); + return MOD_CONT; + } else if (na->status & NS_VERBOTEN) { + notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, na->nick); + return MOD_CONT; + } + + nc = na->nc; + for (access = ci->access, i = 0; i < ci->accesscount; + access++, i++) { + if (access->nc == nc) { + /** + * Patch provided by PopCorn to prevert AOP's reducing SOP's levels + **/ + if ((access->level >= ulev) && (!u->isSuperAdmin)) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + return MOD_CONT; + } + change++; + break; + } + } + + if (!change) { + for (i = 0; i < ci->accesscount; i++) + if (!ci->access[i].in_use) + break; + + if (i == ci->accesscount) { + if (i < CSAccessMax) { + ci->accesscount++; + ci->access = + srealloc(ci->access, + sizeof(ChanAccess) * ci->accesscount); + } else { + notice_lang(s_ChanServ, u, CHAN_XOP_REACHED_LIMIT, + CSAccessMax); + return MOD_CONT; + } + } + + access = &ci->access[i]; + access->nc = nc; + } + + access->in_use = 1; + access->level = xlev; + access->last_seen = 0; + + alog("%s: %s!%s@%s (level %d) %s access level %d to %s (group %s) on channel %s", s_ChanServ, u->nick, u->username, GetHost(u), ulev, change ? "changed" : "set", access->level, na->nick, nc->display, ci->name); + + if (!change) { + notice_lang(s_ChanServ, u, xmsgs[3], access->nc->display, + ci->name); + } else { + notice_lang(s_ChanServ, u, xmsgs[4], access->nc->display, + ci->name); + } + + } else if (stricmp(cmd, "DEL") == 0) { + if (readonly) { + notice_lang(s_ChanServ, u, xmsgs[1]); + return MOD_CONT; + } + + if (ci->accesscount == 0) { + notice_lang(s_ChanServ, u, xmsgs[11], chan); + return MOD_CONT; + } + + ulev = get_access(u, ci); + + /* Special case: is it a number/list? Only do search if it isn't. */ + if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) { + int count, deleted, last = -1, perm = 0; + deleted = + process_numlist(nick, &count, xop_del_callback, u, ci, + &last, &perm, ulev, xlev); + if (!deleted) { + if (perm) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + } else if (count == 1) { + notice_lang(s_ChanServ, u, xmsgs[5], last, ci->name); + } else { + notice_lang(s_ChanServ, u, xmsgs[7], ci->name); + } + } else if (deleted == 1) { + notice_lang(s_ChanServ, u, xmsgs[9], ci->name); + } else { + notice_lang(s_ChanServ, u, xmsgs[10], deleted, ci->name); + } + } else { + na = findnick(nick); + if (!na) { + notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, nick); + return MOD_CONT; + } + nc = na->nc; + + for (i = 0; i < ci->accesscount; i++) + if (ci->access[i].nc == nc && ci->access[i].level == xlev) + break; + + if (i == ci->accesscount) { + notice_lang(s_ChanServ, u, xmsgs[6], nick, chan); + return MOD_CONT; + } + + access = &ci->access[i]; + if (!is_servadmin && ulev <= access->level) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + } else { + notice_lang(s_ChanServ, u, xmsgs[8], access->nc->display, + ci->name); + access->nc = NULL; + access->in_use = 0; + } + } + } else if (stricmp(cmd, "LIST") == 0) { + int sent_header = 0; + + ulev = get_access(u, ci); + + if (!is_servadmin && ulev < ACCESS_AOP) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + return MOD_CONT; + } + + if (ci->accesscount == 0) { + notice_lang(s_ChanServ, u, xmsgs[11], ci->name); + return MOD_CONT; + } + + if (nick && strspn(nick, "1234567890,-") == strlen(nick)) { + process_numlist(nick, NULL, xop_list_callback, u, ci, + &sent_header, xlev, xmsgs[12]); + } else { + for (i = 0; i < ci->accesscount; i++) { + if (nick && ci->access[i].nc + && !match_wild_nocase(nick, ci->access[i].nc->display)) + continue; + xop_list(u, i, ci, &sent_header, xlev, xmsgs[12]); + } + } + if (!sent_header) + notice_lang(s_ChanServ, u, xmsgs[7], chan); + } else if (stricmp(cmd, "CLEAR") == 0) { + if (readonly) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED); + return MOD_CONT; + } + + if (ci->accesscount == 0) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, chan); + return MOD_CONT; + } + + if (!is_servadmin && !is_founder(u, ci)) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + return MOD_CONT; + } + + for (i = 0; i < ci->accesscount; i++) { + if (ci->access[i].in_use && ci->access[i].level == xlev) { + ci->access[i].nc = NULL; + ci->access[i].in_use = 0; + } + } + + notice_lang(s_ChanServ, u, xmsgs[13]); + } else { + syntax_error(s_ChanServ, u, xname, xmsgs[0]); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_aop(User * u) +{ + return do_xop(u, "AOP", ACCESS_AOP, xop_msgs[0]); +} + +/*************************************************************************/ + +#ifdef HAS_HALFOP + +static int do_hop(User * u) +{ + return do_xop(u, "HOP", ACCESS_HOP, xop_msgs[3]); +} + +#endif + +/*************************************************************************/ + +static int do_sop(User * u) +{ + return do_xop(u, "SOP", ACCESS_SOP, xop_msgs[1]); +} + +/*************************************************************************/ + +static int do_vop(User * u) +{ + return do_xop(u, "VOP", ACCESS_VOP, xop_msgs[2]); +} + +/*************************************************************************/ + +/* `last' is set to the last index this routine was called with + * `perm' is incremented whenever a permission-denied error occurs + */ + +static int access_del(User * u, ChanAccess * access, int *perm, int uacc) +{ + if (!access->in_use) + return 0; + if (!is_services_admin(u) && uacc <= access->level) { + (*perm)++; + return 0; + } + access->nc = NULL; + access->in_use = 0; + return 1; +} + +static int access_del_callback(User * u, int num, va_list args) +{ + ChannelInfo *ci = va_arg(args, ChannelInfo *); + int *last = va_arg(args, int *); + int *perm = va_arg(args, int *); + int uacc = va_arg(args, int); + if (num < 1 || num > ci->accesscount) + return 0; + *last = num; + return access_del(u, &ci->access[num - 1], perm, uacc); +} + + +static int access_list(User * u, int index, ChannelInfo * ci, + int *sent_header) +{ + ChanAccess *access = &ci->access[index]; + char *xop; + + if (!access->in_use) + return 0; + + if (!*sent_header) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_HEADER, ci->name); + *sent_header = 1; + } + + if (ci->flags & CI_XOP) { + xop = get_xop_level(access->level); + notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_XOP_FORMAT, index + 1, + xop, access->nc->display); + } else { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_AXS_FORMAT, index + 1, + access->level, access->nc->display); + } + return 1; +} + +static int access_list_callback(User * u, int num, va_list args) +{ + ChannelInfo *ci = va_arg(args, ChannelInfo *); + int *sent_header = va_arg(args, int *); + if (num < 1 || num > ci->accesscount) + return 0; + return access_list(u, num - 1, ci, sent_header); +} + + +static int do_access(User * u) +{ + char *chan = strtok(NULL, " "); + char *cmd = strtok(NULL, " "); + char *nick = strtok(NULL, " "); + char *s = strtok(NULL, " "); + + ChannelInfo *ci; + NickAlias *na; + NickCore *nc; + ChanAccess *access; + + int i; + short level = 0, ulev; + int is_list = (cmd && stricmp(cmd, "LIST") == 0); + int is_servadmin = is_services_admin(u); + + /* If LIST, we don't *require* any parameters, but we can take any. + * If DEL, we require a nick and no level. + * Else (ADD), we require a level (which implies a nick). */ + if (!cmd || ((is_list || !stricmp(cmd, "CLEAR")) ? 0 : + (stricmp(cmd, "DEL") == 0) ? (!nick || s) : !s)) { + syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_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); + /* We still allow LIST in xOP mode, but not others */ + } else if ((ci->flags & CI_XOP) && !is_list) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_XOP, s_ChanServ); + } else if (((is_list && !check_access(u, ci, CA_ACCESS_LIST)) + || (!is_list && !check_access(u, ci, CA_ACCESS_CHANGE))) + && !is_servadmin) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else if (stricmp(cmd, "ADD") == 0) { + if (readonly) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED); + return MOD_CONT; + } + + level = atoi(s); + ulev = get_access(u, ci); + + if (!is_servadmin && level >= ulev) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + return MOD_CONT; + } + + if (level == 0) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_NONZERO); + return MOD_CONT; + } else if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_RANGE, + ACCESS_INVALID + 1, ACCESS_FOUNDER - 1); + return MOD_CONT; + } + + na = findnick(nick); + if (!na) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_NICKS_ONLY); + return MOD_CONT; + } + if (na->status & NS_VERBOTEN) { + notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, nick); + return MOD_CONT; + } + + nc = na->nc; + for (access = ci->access, i = 0; i < ci->accesscount; + access++, i++) { + if (access->nc == nc) { + /* Don't allow lowering from a level >= ulev */ + if (!is_servadmin && access->level >= ulev) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + return MOD_CONT; + } + if (access->level == level) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_UNCHANGED, + access->nc->display, chan, level); + return MOD_CONT; + } + access->level = level; + alog("%s: %s!%s@%s (level %d) set access level %d to %s (group %s) on channel %s", s_ChanServ, u->nick, u->username, GetHost(u), ulev, access->level, na->nick, nc->display, ci->name); + notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_CHANGED, + access->nc->display, chan, level); + return MOD_CONT; + } + } + + for (i = 0; i < ci->accesscount; i++) { + if (!ci->access[i].in_use) + break; + } + if (i == ci->accesscount) { + if (i < CSAccessMax) { + ci->accesscount++; + ci->access = + srealloc(ci->access, + sizeof(ChanAccess) * ci->accesscount); + } else { + notice_lang(s_ChanServ, u, CHAN_ACCESS_REACHED_LIMIT, + CSAccessMax); + return MOD_CONT; + } + } + + access = &ci->access[i]; + access->nc = nc; + access->in_use = 1; + access->level = level; + access->last_seen = 0; + + alog("%s: %s!%s@%s (level %d) set access level %d to %s (group %s) on channel %s", s_ChanServ, u->nick, u->username, GetHost(u), ulev, access->level, na->nick, nc->display, ci->name); + notice_lang(s_ChanServ, u, CHAN_ACCESS_ADDED, nc->display, + ci->name, access->level); + } else if (stricmp(cmd, "DEL") == 0) { + + if (readonly) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED); + return MOD_CONT; + } + + if (ci->accesscount == 0) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, chan); + return MOD_CONT; + } + + /* Special case: is it a number/list? Only do search if it isn't. */ + if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) { + int count, deleted, last = -1, perm = 0; + deleted = process_numlist(nick, &count, access_del_callback, u, + ci, &last, &perm, get_access(u, ci)); + if (!deleted) { + if (perm) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + } else if (count == 1) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_SUCH_ENTRY, + last, ci->name); + } else { + notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_MATCH, + ci->name); + } + } else if (deleted == 1) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED_ONE, + ci->name); + } else { + notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED_SEVERAL, + deleted, ci->name); + } + } else { + na = findnick(nick); + if (!na) { + notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, nick); + return MOD_CONT; + } + nc = na->nc; + for (i = 0; i < ci->accesscount; i++) { + if (ci->access[i].nc == nc) + break; + } + if (i == ci->accesscount) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_NOT_FOUND, nick, + chan); + return MOD_CONT; + } + access = &ci->access[i]; + if (!is_servadmin && get_access(u, ci) <= access->level) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + } else { + notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED, + access->nc->display, ci->name); + alog("%s: %s!%s@%s (level %d) deleted access of %s (group %s) on %s", s_ChanServ, u->nick, u->username, GetHost(u), get_access(u, ci), na->nick, access->nc->display, chan); + access->nc = NULL; + access->in_use = 0; + } + } + } else if (stricmp(cmd, "LIST") == 0) { + int sent_header = 0; + + if (ci->accesscount == 0) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, chan); + return MOD_CONT; + } + if (nick && strspn(nick, "1234567890,-") == strlen(nick)) { + process_numlist(nick, NULL, access_list_callback, u, ci, + &sent_header); + } else { + for (i = 0; i < ci->accesscount; i++) { + if (nick && ci->access[i].nc + && !match_wild_nocase(nick, ci->access[i].nc->display)) + continue; + access_list(u, i, ci, &sent_header); + } + } + if (!sent_header) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_MATCH, chan); + } else { + notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_FOOTER, ci->name); + } + } else if (stricmp(cmd, "CLEAR") == 0) { + + if (readonly) { + notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED); + return MOD_CONT; + } + + if (!is_servadmin && !is_founder(u, ci)) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + return MOD_CONT; + } + + free(ci->access); + ci->access = NULL; + ci->accesscount = 0; + + notice_lang(s_ChanServ, u, CHAN_ACCESS_CLEAR); + alog("%s: %s!%s@%s (level %d) cleared access list on %s", + s_ChanServ, u->nick, u->username, GetHost(u), get_access(u, + ci), + chan); + + } else { + syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +/* Is the mask stuck? */ + +AutoKick *is_stuck(ChannelInfo * ci, char *mask) +{ + int i; + AutoKick *akick; + + for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) { + if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK) + || !(akick->flags & AK_STUCK)) + continue; + /* Example: mask = *!*@*.org and akick->u.mask = *!*@*.epona.org */ + if (match_wild_nocase(mask, akick->u.mask)) + return akick; +#ifdef IRC_DREAMFORGE + /* Example: mask = *!*@irc.epona.org and akick->u.mask = *!*@*.epona.org */ + if (match_wild_nocase(akick->u.mask, mask)) + return akick; +#endif + } + + return NULL; +} + +/* Ban the stuck mask in a safe manner. */ + +void stick_mask(ChannelInfo * ci, AutoKick * akick) +{ + int i; + char *av[2]; + + for (i = 0; i < ci->c->bancount; i++) { + /* If akick is already covered by a wider ban. + Example: c->bans[i] = *!*@*.org and akick->u.mask = *!*@*.epona.org */ + if (match_wild_nocase(ci->c->bans[i], akick->u.mask)) + return; + +#ifdef IRC_DREAMFORGE + /* If akick is wider than a ban already in place. + Example: c->bans[i] = *!*@irc.epona.org and akick->u.mask = *!*@*.epona.org */ + if (match_wild_nocase(akick->u.mask, ci->c->bans[i])) + return; +#endif + } + + /* Falling there means set the ban */ + + av[0] = sstrdup("+b"); + av[1] = akick->u.mask; + send_cmd(whosends(ci), "MODE %s +b %s", ci->c->name, akick->u.mask); + chan_set_modes(s_ChanServ, ci->c, 2, av, 1); + free(av[0]); +} + +/* Ban the stuck mask in a safe manner. */ + +void stick_all(ChannelInfo * ci) +{ + int i; + char *av[2]; + AutoKick *akick; + + for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) { + if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK) + || !(akick->flags & AK_STUCK)) + continue; + + av[0] = sstrdup("+b"); + av[1] = akick->u.mask; + send_cmd(whosends(ci), "MODE %s +b %s", ci->c->name, + akick->u.mask); + chan_set_modes(s_ChanServ, ci->c, 2, av, 1); + free(av[0]); + } +} + +/* `last' is set to the last index this routine was called with */ +static int akick_del(User * u, AutoKick * akick) +{ + if (!(akick->flags & AK_USED)) + return 0; + if (akick->flags & AK_ISNICK) { + akick->u.nc = NULL; + } else { + free(akick->u.mask); + akick->u.mask = NULL; + } + if (akick->reason) { + free(akick->reason); + akick->reason = NULL; + } + if (akick->creator) { + free(akick->creator); + akick->creator = NULL; + } + akick->addtime = 0; + akick->flags = 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) +{ + AutoKick *akick = &ci->akick[index]; + + if (!(akick->flags & AK_USED)) + return 0; + if (!*sent_header) { + notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_HEADER, ci->name); + *sent_header = 1; + } + + notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_FORMAT, index + 1, + ((akick->flags & AK_ISNICK) ? akick->u.nc-> + display : akick->u.mask), + (akick->reason ? akick-> + reason : getstring(u->na, NO_REASON))); + 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 *); + if (num < 1 || num > ci->akickcount) + return 0; + return akick_list(u, num - 1, ci, sent_header); +} + +static int akick_view(User * u, int index, ChannelInfo * ci, + int *sent_header) +{ + AutoKick *akick = &ci->akick[index]; + char timebuf[64]; + struct tm tm; + + if (!(akick->flags & AK_USED)) + return 0; + if (!*sent_header) { + notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_HEADER, ci->name); + *sent_header = 1; + } + + if (akick->addtime) { + tm = *localtime(&akick->addtime); + strftime_lang(timebuf, sizeof(timebuf), u, + STRFTIME_SHORT_DATE_FORMAT, &tm); + } else { + snprintf(timebuf, sizeof(timebuf), getstring(u->na, UNKNOWN)); + } + + notice_lang(s_ChanServ, u, + ((akick-> + flags & AK_STUCK) ? CHAN_AKICK_VIEW_FORMAT_STUCK : + CHAN_AKICK_VIEW_FORMAT), index + 1, + ((akick->flags & AK_ISNICK) ? akick->u.nc-> + display : akick->u.mask), + akick->creator ? akick->creator : getstring(u->na, + UNKNOWN), + timebuf, + (akick->reason ? akick-> + reason : getstring(u->na, NO_REASON))); + return 1; +} + +static int akick_view_callback(User * u, int num, va_list args) +{ + ChannelInfo *ci = va_arg(args, ChannelInfo *); + int *sent_header = va_arg(args, int *); + if (num < 1 || num > ci->akickcount) + return 0; + return akick_view(u, num - 1, ci, sent_header); +} + +static int do_akick(User * u) +{ + char *chan = strtok(NULL, " "); + char *cmd = strtok(NULL, " "); + char *mask = strtok(NULL, " "); + char *reason = strtok(NULL, ""); + ChannelInfo *ci; + AutoKick *akick; + int i; + + if (!cmd || (!mask && (!stricmp(cmd, "ADD") || !stricmp(cmd, "STICK") + || !stricmp(cmd, "UNSTICK") + || !stricmp(cmd, "DEL")))) { + + 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)) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else if (stricmp(cmd, "ADD") == 0) { + + NickAlias *na = findnick(mask); + NickCore *nc = NULL; + char *nick, *user, *host; + + if (readonly) { + notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED); + return MOD_CONT; + } + + if (!na) { + split_usermask(mask, &nick, &user, &host); + mask = + scalloc(strlen(nick) + strlen(user) + strlen(host) + 3, 1); + sprintf(mask, "%s!%s@%s", nick, user, host); + free(nick); + free(user); + free(host); + } else { + if (na->status & NS_VERBOTEN) { + notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, mask); + return MOD_CONT; + } + nc = na->nc; + } + + /* Check excepts BEFORE we get this far */ +#if defined (IRC_ULTIMATE) || defined(IRC_ULTIMATE3) || defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_HYBRID) + if (is_excepted_mask(ci, mask) == 1) { + notice_lang(s_ChanServ, u, CHAN_EXCEPTED, mask, chan); + return MOD_CONT; + } +#endif + + for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) { + if (!(akick->flags & AK_USED)) + continue; + if ((akick->flags & AK_ISNICK) ? akick->u.nc == nc + : stricmp(akick->u.mask, mask) == 0) { + notice_lang(s_ChanServ, u, CHAN_AKICK_ALREADY_EXISTS, + (akick->flags & AK_ISNICK) ? akick->u.nc-> + display : akick->u.mask, chan); + return MOD_CONT; + } + } + + for (i = 0; i < ci->akickcount; i++) { + if (!(ci->akick[i].flags & AK_USED)) + break; + } + if (i == ci->akickcount) { + if (ci->akickcount >= CSAutokickMax) { + notice_lang(s_ChanServ, u, CHAN_AKICK_REACHED_LIMIT, + CSAutokickMax); + return MOD_CONT; + } + ci->akickcount++; + ci->akick = + srealloc(ci->akick, sizeof(AutoKick) * ci->akickcount); + } + akick = &ci->akick[i]; + akick->flags = AK_USED; + if (nc) { + akick->flags |= AK_ISNICK; + akick->u.nc = nc; + } else { + akick->u.mask = mask; + } + akick->creator = sstrdup(u->nick); + akick->addtime = time(NULL); + if (reason) { + if (strlen(reason) > 200) + reason[200] = '\0'; + akick->reason = sstrdup(reason); + } else { + akick->reason = NULL; + } + notice_lang(s_ChanServ, u, CHAN_AKICK_ADDED, mask, chan); + + } else if (stricmp(cmd, "STICK") == 0) { + NickAlias *na; + NickCore *nc; + + if (readonly) { + notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED); + return MOD_CONT; + } + + if (ci->akickcount == 0) { + notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, ci->name); + return MOD_CONT; + } + + na = findnick(mask); + nc = (na ? na->nc : NULL); + + for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) { + if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK)) + continue; + if (!stricmp(akick->u.mask, mask)) + break; + } + + if (i == ci->akickcount) { + notice_lang(s_ChanServ, u, CHAN_AKICK_NOT_FOUND, mask, + ci->name); + return MOD_CONT; + } + + akick->flags |= AK_STUCK; + notice_lang(s_ChanServ, u, CHAN_AKICK_STUCK, akick->u.mask, + ci->name); + + if (ci->c) + stick_mask(ci, akick); + } else if (stricmp(cmd, "UNSTICK") == 0) { + NickAlias *na; + NickCore *nc; + + if (readonly) { + notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED); + return MOD_CONT; + } + + if (ci->akickcount == 0) { + notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, ci->name); + return MOD_CONT; + } + + na = findnick(mask); + nc = (na ? na->nc : NULL); + + for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) { + if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK)) + continue; + if (!stricmp(akick->u.mask, mask)) + break; + } + + if (i == ci->akickcount) { + notice_lang(s_ChanServ, u, CHAN_AKICK_NOT_FOUND, mask, + ci->name); + return MOD_CONT; + } + + akick->flags &= ~AK_STUCK; + notice_lang(s_ChanServ, u, CHAN_AKICK_UNSTUCK, akick->u.mask, + ci->name); + + } else if (stricmp(cmd, "DEL") == 0) { + + if (readonly) { + notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED); + return MOD_CONT; + } + + if (ci->akickcount == 0) { + notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, chan); + return MOD_CONT; + } + + /* 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 { + NickAlias *na = findnick(mask); + NickCore *nc = (na ? na->nc : NULL); + + for (akick = ci->akick, i = 0; i < ci->akickcount; + akick++, i++) { + if (!(akick->flags & AK_USED)) + continue; + if (((akick->flags & AK_ISNICK) && akick->u.nc == nc) + || (!(akick->flags & AK_ISNICK) + && stricmp(akick->u.mask, mask) == 0)) + break; + } + if (i == ci->akickcount) { + notice_lang(s_ChanServ, u, CHAN_AKICK_NOT_FOUND, mask, + chan); + return MOD_CONT; + } + notice_lang(s_ChanServ, u, CHAN_AKICK_DELETED, mask, chan); + akick_del(u, akick); + } + + } else if (stricmp(cmd, "LIST") == 0) { + int sent_header = 0; + + if (ci->akickcount == 0) { + notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, chan); + return MOD_CONT; + } + if (mask && isdigit(*mask) && + strspn(mask, "1234567890,-") == strlen(mask)) { + process_numlist(mask, NULL, akick_list_callback, u, ci, + &sent_header); + } else { + for (akick = ci->akick, i = 0; i < ci->akickcount; + akick++, i++) { + if (!(akick->flags & AK_USED)) + continue; + if (mask) { + if (!(akick->flags & AK_ISNICK) + && !match_wild_nocase(mask, akick->u.mask)) + continue; + if ((akick->flags & AK_ISNICK) + && !match_wild_nocase(mask, akick->u.nc->display)) + continue; + } + akick_list(u, i, ci, &sent_header); + } + } + if (!sent_header) + notice_lang(s_ChanServ, u, CHAN_AKICK_NO_MATCH, chan); + + } else if (stricmp(cmd, "VIEW") == 0) { + int sent_header = 0; + + if (ci->akickcount == 0) { + notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, chan); + return MOD_CONT; + } + if (mask && isdigit(*mask) && + strspn(mask, "1234567890,-") == strlen(mask)) { + process_numlist(mask, NULL, akick_view_callback, u, ci, + &sent_header); + } else { + for (akick = ci->akick, i = 0; i < ci->akickcount; + akick++, i++) { + if (!(akick->flags & AK_USED)) + continue; + if (mask) { + if (!(akick->flags & AK_ISNICK) + && !match_wild_nocase(mask, akick->u.mask)) + continue; + if ((akick->flags & AK_ISNICK) + && !match_wild_nocase(mask, akick->u.nc->display)) + continue; + } + akick_view(u, i, ci, &sent_header); + } + } + 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 MOD_CONT; + } + + cu = c->users; + + while (cu) { + next = cu->next; + if (check_kick(cu->user, c->name)) { + argv[0] = sstrdup(c->name); + argv[1] = sstrdup(cu->user->nick); + argv[2] = sstrdup(CSAutokickReason); + + do_kick(s_ChanServ, 3, argv); + + free(argv[2]); + free(argv[1]); + free(argv[0]); + + count++; + } + cu = next; + } + + notice_lang(s_ChanServ, u, CHAN_AKICK_ENFORCE_DONE, chan, count); + + } else if (stricmp(cmd, "CLEAR") == 0) { + + if (readonly) { + notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED); + return MOD_CONT; + } + + for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) { + if (!(akick->flags & AK_USED)) + continue; + akick_del(u, akick); + } + + free(ci->akick); + ci->akick = NULL; + ci->akickcount = 0; + + notice_lang(s_ChanServ, u, CHAN_AKICK_CLEAR); + + } else { + syntax_error(s_ChanServ, u, "AKICK", CHAN_AKICK_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_levels(User * u) +{ + char *chan = strtok(NULL, " "); + char *cmd = strtok(NULL, " "); + char *what = strtok(NULL, " "); + char *s = strtok(NULL, " "); + char *error; + + ChannelInfo *ci; + short level; + int i; + + /* If SET, we want two extra parameters; if DIS[ABLE] or FOUNDER, 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 (ci->flags & CI_XOP) { + notice_lang(s_ChanServ, u, CHAN_LEVELS_XOP); + } else if (!is_founder(u, ci) && !is_services_admin(u)) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else if (stricmp(cmd, "SET") == 0) { + level = strtol(s, &error, 10); + + if (*error != '\0') { + syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX); + return MOD_CONT; + } + + if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER) { + notice_lang(s_ChanServ, u, CHAN_LEVELS_RANGE, + ACCESS_INVALID + 1, ACCESS_FOUNDER - 1); + return MOD_CONT; + } + + for (i = 0; levelinfo[i].what >= 0; i++) { + if (stricmp(levelinfo[i].name, what) == 0) { + ci->levels[levelinfo[i].what] = level; + + alog("%s: %s!%s@%s set level %s on channel %s to %d", + s_ChanServ, u->nick, u->username, GetHost(u), + levelinfo[i].name, ci->name, level); + notice_lang(s_ChanServ, u, CHAN_LEVELS_CHANGED, + levelinfo[i].name, chan, level); + return MOD_CONT; + } + } + + 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] = ACCESS_INVALID; + + alog("%s: %s!%s@%s disabled level %s on channel %s", + s_ChanServ, u->nick, u->username, GetHost(u), + levelinfo[i].name, ci->name); + notice_lang(s_ChanServ, u, CHAN_LEVELS_DISABLED, + levelinfo[i].name, chan); + return MOD_CONT; + } + } + + 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 == ACCESS_INVALID) { + j = levelinfo[i].what; + + if (j == CA_AUTOOP || j == CA_AUTODEOP || j == CA_AUTOVOICE + || j == CA_NOJOIN) { + notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_DISABLED, + levelinfo_maxwidth, levelinfo[i].name); + } else { + notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_DISABLED, + levelinfo_maxwidth, levelinfo[i].name); + } + } else if (j == ACCESS_FOUNDER) { + notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_FOUNDER, + 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); + + alog("%s: %s!%s@%s reset levels definitions on channel %s", + s_ChanServ, u->nick, u->username, GetHost(u), ci->name); + notice_lang(s_ChanServ, u, CHAN_LEVELS_RESET, chan); + } else { + syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +/* SADMINS and users, who have identified for a channel, can now cause it's + * Enstry Message and Successor to be displayed by supplying the ALL parameter. + * Syntax: INFO channel [ALL] + * -TheShadow (29 Mar 1999) + */ + +static int do_info(User * u) +{ + char *chan = strtok(NULL, " "); + char *param = strtok(NULL, " "); + ChannelInfo *ci; + char buf[BUFSIZE], *end; + struct tm *tm; + int need_comma = 0; + const char *commastr = getstring(u->na, COMMA_SPACE); + int is_servadmin = is_services_admin(u); + int show_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) { + if (is_oper(u) && ci->forbidby) + notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN_OPER, chan, + ci->forbidby, + (ci->forbidreason ? ci-> + forbidreason : getstring(u->na, NO_REASON))); + else + 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 { + + /* Should we show all fields? Only for sadmins and identified users */ + if (param && stricmp(param, "ALL") == 0 && + (check_access(u, ci, CA_INFO) || is_servadmin)) + show_all = 1; + + notice_lang(s_ChanServ, u, CHAN_INFO_HEADER, chan); + notice_lang(s_ChanServ, u, CHAN_INFO_NO_FOUNDER, + ci->founder->display); + + if (show_all && ci->successor) + notice_lang(s_ChanServ, u, CHAN_INFO_NO_SUCCESSOR, + ci->successor->display); + + 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); + if (ci->last_topic && (show_all || (!(ci->mlock_on & CMODE_s) + && (!ci->c + || !(ci->c-> + mode & CMODE_s))))) { + 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 && 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); + + if (show_all) { + notice_lang(s_ChanServ, u, CHAN_INFO_BANTYPE, ci->bantype); + + end = buf; + *end = 0; + if (ci->flags & CI_KEEPTOPIC) { + end += snprintf(end, sizeof(buf) - (end - buf), "%s", + getstring(u->na, CHAN_INFO_OPT_KEEPTOPIC)); + need_comma = 1; + } + if (ci->flags & CI_OPNOTICE) { + end += snprintf(end, sizeof(buf) - (end - buf), "%s%s", + need_comma ? commastr : "", + getstring(u->na, CHAN_INFO_OPT_OPNOTICE)); + need_comma = 1; + } + if (ci->flags & CI_PEACE) { + end += snprintf(end, sizeof(buf) - (end - buf), "%s%s", + need_comma ? commastr : "", + getstring(u->na, CHAN_INFO_OPT_PEACE)); + need_comma = 1; + } + if (ci->flags & CI_PRIVATE) { + end += snprintf(end, sizeof(buf) - (end - buf), "%s%s", + need_comma ? commastr : "", + getstring(u->na, CHAN_INFO_OPT_PRIVATE)); + need_comma = 1; + } + if (ci->flags & CI_RESTRICTED) { + end += snprintf(end, sizeof(buf) - (end - buf), "%s%s", + need_comma ? commastr : "", + getstring(u->na, + CHAN_INFO_OPT_RESTRICTED)); + need_comma = 1; + } + if (ci->flags & CI_SECURE) { + end += snprintf(end, sizeof(buf) - (end - buf), "%s%s", + need_comma ? commastr : "", + getstring(u->na, CHAN_INFO_OPT_SECURE)); + need_comma = 1; + } + if (ci->flags & CI_SECUREOPS) { + end += snprintf(end, sizeof(buf) - (end - buf), "%s%s", + need_comma ? commastr : "", + getstring(u->na, CHAN_INFO_OPT_SECUREOPS)); + need_comma = 1; + } + if (ci->flags & CI_SECUREFOUNDER) { + end += snprintf(end, sizeof(buf) - (end - buf), "%s%s", + need_comma ? commastr : "", + getstring(u->na, + CHAN_INFO_OPT_SECUREFOUNDER)); + need_comma = 1; + } + if ((ci->flags & CI_SIGNKICK) + || (ci->flags & CI_SIGNKICK_LEVEL)) { + end += + snprintf(end, sizeof(buf) - (end - buf), "%s%s", + need_comma ? commastr : "", getstring(u->na, + CHAN_INFO_OPT_SIGNKICK)); + need_comma = 1; + } + if (ci->flags & CI_TOPICLOCK) { + end += snprintf(end, sizeof(buf) - (end - buf), "%s%s", + need_comma ? commastr : "", + getstring(u->na, CHAN_INFO_OPT_TOPICLOCK)); + need_comma = 1; + } + if (ci->flags & CI_XOP) { + end += snprintf(end, sizeof(buf) - (end - buf), "%s%s", + need_comma ? commastr : "", + getstring(u->na, CHAN_INFO_OPT_XOP)); + need_comma = 1; + } + notice_lang(s_ChanServ, u, CHAN_INFO_OPTIONS, + *buf ? buf : getstring(u->na, CHAN_INFO_OPT_NONE)); + notice_lang(s_ChanServ, u, CHAN_INFO_MODE_LOCK, + get_mlock_modes(ci, 1)); + + } + + if ((ci->flags & CI_NO_EXPIRE) && show_all) + notice_lang(s_ChanServ, u, CHAN_INFO_NO_EXPIRE); + if (ci->flags & CI_SUSPENDED) { + notice_lang(s_ChanServ, u, CHAN_X_SUSPENDED, ci->forbidby, + (ci->forbidreason ? ci-> + forbidreason : getstring(u->na, NO_REASON))); + } + + if (!show_all && (check_access(u, ci, CA_INFO) || is_servadmin)) + notice_lang(s_ChanServ, u, NICK_INFO_FOR_MORE, s_ChanServ, + ci->name); + + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_list(User * u) +{ + char *pattern = strtok(NULL, " "); + ChannelInfo *ci; + int nchans, i; + char buf[BUFSIZE]; + int is_servadmin = is_services_admin(u); + int count = 0, from = 0, to = 0; + char *tmp = NULL; + char *s = NULL; + char *keyword; + int32 matchflags = 0; + + + if (CSListOpersOnly && (!u || !is_oper(u))) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + return MOD_CONT; + } + + if (!pattern) { + syntax_error(s_ChanServ, u, "LIST", + is_servadmin ? CHAN_LIST_SERVADMIN_SYNTAX : + CHAN_LIST_SYNTAX); + } else { + + if (pattern) { + if (pattern[0] == '#') { + tmp = myStrGetOnlyToken((pattern + 1), '-', 0); /* Read FROM out */ + if (!tmp) { + return MOD_CONT; + } + for (s = tmp; *s; s++) { + if (!isdigit(*s)) { + return MOD_CONT; + } + } + from = atoi(tmp); + tmp = myStrGetTokenRemainder(pattern, '-', 1); /* Read TO out */ + if (!tmp) { + return MOD_CONT; + } + for (s = tmp; *s; s++) { + if (!isdigit(*s)) { + return MOD_CONT; + } + } + to = atoi(tmp); + pattern = sstrdup("*"); + } + } + + nchans = 0; + + while (is_servadmin && (keyword = strtok(NULL, " "))) { + if (stricmp(keyword, "FORBIDDEN") == 0) + matchflags |= CI_VERBOTEN; + if (stricmp(keyword, "SUSPENDED") == 0) + matchflags |= CI_SUSPENDED; + if (stricmp(keyword, "NOEXPIRE") == 0) + matchflags |= CI_NO_EXPIRE; + } + + notice_lang(s_ChanServ, u, CHAN_LIST_HEADER, pattern); + for (i = 0; i < 256; i++) { + for (ci = chanlists[i]; ci; ci = ci->next) { + if (!is_servadmin && ((ci->flags & CI_PRIVATE) + || (ci->flags & CI_VERBOTEN))) + continue; + if ((matchflags != 0) && !(ci->flags & matchflags)) + continue; + + if (stricmp(pattern, ci->name) == 0 + || match_wild_nocase(pattern, ci->name)) { + if ((((count + 1 >= from) && (count + 1 <= to)) + || ((from == 0) && (to == 0))) + && (++nchans <= CSListMax)) { + char noexpire_char = ' '; + if (is_servadmin && (ci->flags & CI_NO_EXPIRE)) + noexpire_char = '!'; + + if (ci->flags & CI_VERBOTEN) { + snprintf(buf, sizeof(buf), + "%-20s [Forbidden]", ci->name); + } else if (ci->flags & CI_SUSPENDED) { + snprintf(buf, sizeof(buf), + "%-20s [Suspended]", ci->name); + } else { + snprintf(buf, sizeof(buf), "%-20s %s", + ci->name, ci->desc ? ci->desc : ""); + } + + notice_user(s_ChanServ, u, " %c%s", + noexpire_char, buf); + } + count++; + } + } + } + notice_lang(s_ChanServ, u, CHAN_LIST_END, + nchans > CSListMax ? CSListMax : nchans, nchans); + } + return MOD_CONT; + +} + +/*************************************************************************/ + +static int 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 (!(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(whosends(ci), "INVITE %s %s", u->nick, chan); + } + return MOD_CONT; +} + +/*************************************************************************/ + +/* do_util: not a command, but does the job of other */ + +static int do_util(User * u, CSModeUtil * util) +{ + char *av[2]; + char *chan = strtok(NULL, " "); + char *nick = strtok(NULL, " "); + + Channel *c; + ChannelInfo *ci; + User *u2; + + int is_same; + + if (!chan) { + struct u_chanlist *uc; + + av[0] = util->mode; + av[1] = u->nick; + + /* Sets the mode to the user on every channels he is on. */ + + for (uc = u->chans; uc; uc = uc->next) { + if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN) + && check_access(u, ci, util->levelself)) { + send_cmd(whosends(ci), "MODE %s %s %s", uc->chan->name, + util->mode, u->nick); + chan_set_modes(s_ChanServ, uc->chan, 2, av, 1); + + if (util->notice && ci->flags & util->notice) + notice(whosends(ci), chan, + "%s command used for %s by %s", util->name, + u->nick, u->nick); + } + } + + return MOD_CONT; + } else if (!nick) { + nick = u->nick; + } + + is_same = (nick == u->nick) ? 1 : (stricmp(nick, u->nick) == 0); + + if (!(c = findchan(chan))) { + notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan); + } else if (!(ci = c->ci)) { + notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, c->name); + } else if (ci->flags & CI_VERBOTEN) { + notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, ci->name); + } else if (is_same ? !(u2 = u) : !(u2 = finduser(nick))) { + notice_lang(s_ChanServ, u, NICK_X_NOT_IN_USE, nick); + } else if (!is_on_chan(c, u2)) { + notice_lang(s_ChanServ, u, NICK_X_NOT_ON_CHAN, u2->nick, c->name); + } else if (is_same ? !check_access(u, ci, util->levelself) : + !check_access(u, ci, util->level)) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else if (*util->mode == '-' && !is_same && (ci->flags & CI_PEACE) + && (get_access(u2, ci) >= get_access(u, ci))) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); +#if defined (IRC_ULTIMATE) || defined (IRC_ULTIMATE3) + } else if (*util->mode == '-' && is_protected(u2)) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); +#endif + } else { + send_cmd(whosends(ci), "MODE %s %s %s", c->name, util->mode, + u2->nick); + + av[0] = util->mode; + av[1] = u2->nick; + chan_set_modes(s_ChanServ, c, 2, av, 1); + + if (util->notice && ci->flags & util->notice) + notice(whosends(ci), c->name, "%s command used for %s by %s", + util->name, u2->nick, u->nick); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_op(User * u) +{ + return do_util(u, &csmodeutils[MUT_OP]); +} + +/*************************************************************************/ + +static int do_deop(User * u) +{ + return do_util(u, &csmodeutils[MUT_DEOP]); +} + +/*************************************************************************/ + +static int do_voice(User * u) +{ + return do_util(u, &csmodeutils[MUT_VOICE]); +} + +/*************************************************************************/ + +static int do_devoice(User * u) +{ + return do_util(u, &csmodeutils[MUT_DEVOICE]); +} + +/*************************************************************************/ + +#ifdef HAS_HALFOP + +static int do_halfop(User * u) +{ + return do_util(u, &csmodeutils[MUT_HALFOP]); +} + +/*************************************************************************/ + +static int do_dehalfop(User * u) +{ + return do_util(u, &csmodeutils[MUT_DEHALFOP]); +} + +#endif + +/*************************************************************************/ + +#if defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_VIAGRA) + +static int do_protect(User * u) +{ + return do_util(u, &csmodeutils[MUT_PROTECT]); +} + +/*************************************************************************/ + +static int do_deprotect(User * u) +{ + return do_util(u, &csmodeutils[MUT_DEPROTECT]); +} + +/*************************************************************************/ +#endif + +#if defined(IRC_UNREAL) || defined(IRC_VIAGRA) + +static int do_owner(User * u) +{ + char *av[2]; + char *chan = strtok(NULL, " "); + + Channel *c; + ChannelInfo *ci; + + if (!chan) { + struct u_chanlist *uc; + + av[0] = sstrdup("+q"); + av[1] = u->nick; + + /* Sets the mode to the user on every channels he is on. */ + + for (uc = u->chans; uc; uc = uc->next) { + if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN) + && is_founder(u, ci)) { + send_cmd(whosends(ci), "MODE %s %s %s", uc->chan->name, + av[0], u->nick); + chan_set_modes(s_ChanServ, uc->chan, 2, av, 1); + } + } + + free(av[0]); + return MOD_CONT; + } + + if (!(c = findchan(chan))) { + notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan); + } else if (!(ci = c->ci)) { + notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, c->name); + } else if (ci->flags & CI_VERBOTEN) { + notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, ci->name); + } else if (!is_on_chan(c, u)) { + notice_lang(s_ChanServ, u, NICK_X_NOT_ON_CHAN, u->nick, c->name); + } else if (!is_founder(u, ci)) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else { + send_cmd(whosends(ci), "MODE %s +q %s", c->name, u->nick); + + av[0] = sstrdup("+q"); + av[1] = u->nick; + chan_set_modes(s_ChanServ, c, 2, av, 1); + free(av[0]); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_deowner(User * u) +{ + char *av[2]; + char *chan = strtok(NULL, " "); + + Channel *c; + ChannelInfo *ci; + + if (!chan) { + struct u_chanlist *uc; + + av[0] = sstrdup("-q"); + av[1] = u->nick; + + /* Sets the mode to the user on every channels he is on. */ + + for (uc = u->chans; uc; uc = uc->next) { + if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN) + && is_founder(u, ci)) { + send_cmd(whosends(ci), "MODE %s %s %s", uc->chan->name, + av[0], u->nick); + chan_set_modes(s_ChanServ, uc->chan, 2, av, 1); + } + } + + free(av[0]); + return MOD_CONT; + } + + if (!(c = findchan(chan))) { + notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan); + } else if (!(ci = c->ci)) { + notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, c->name); + } else if (ci->flags & CI_VERBOTEN) { + notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, ci->name); + } else if (!is_on_chan(c, u)) { + notice_lang(s_ChanServ, u, NICK_X_NOT_ON_CHAN, u->nick, c->name); + } else if (!is_founder(u, ci)) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else { + send_cmd(whosends(ci), "MODE %s -q %s", c->name, u->nick); + + av[0] = sstrdup("-q"); + av[1] = u->nick; + chan_set_modes(s_ChanServ, c, 2, av, 1); + free(av[0]); + } + return MOD_CONT; +} + +#endif + +/*************************************************************************/ + +static int do_cs_kick(User * u) +{ + char *chan = strtok(NULL, " "); + char *params = strtok(NULL, " "); + char *reason = strtok(NULL, ""); + + Channel *c; + ChannelInfo *ci; + User *u2; + + int is_same; + + if (!reason) { + reason = "Requested"; + } else { + if (strlen(reason) > 200) + reason[200] = '\0'; + } + + if (!chan) { + struct u_chanlist *uc, *next; + + /* Kicks the user on every channels he is on. */ + + for (uc = u->chans; uc; uc = next) { + next = uc->next; + if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN) + && check_access(u, ci, CA_KICKME)) { + char *av[3]; + + if ((ci->flags & CI_SIGNKICK) + || ((ci->flags & CI_SIGNKICK_LEVEL) + && !check_access(u, ci, CA_SIGNKICK))) + send_cmd(whosends(ci), "KICK %s %s :%s (%s)", ci->name, + u->nick, reason, u->nick); + else + send_cmd(whosends(ci), "KICK %s %s :%s", ci->name, + u->nick, reason); + av[0] = ci->name; + av[1] = u->nick; + av[2] = reason; + do_kick(s_ChanServ, 3, av); + } + } + + return MOD_CONT; + } else if (!params) { + params = u->nick; + } + + is_same = (params == u->nick) ? 1 : (stricmp(params, u->nick) == 0); + + if (!(c = findchan(chan))) { + notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan); + } 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 (is_same ? !(u2 = u) : !(u2 = finduser(params))) { + notice_lang(s_ChanServ, u, NICK_X_NOT_IN_USE, params); + } else if (!is_on_chan(c, u2)) { + notice_lang(s_ChanServ, u, NICK_X_NOT_ON_CHAN, u2->nick, c->name); + } else if (!is_same ? !check_access(u, ci, CA_KICK) : + !check_access(u, ci, CA_KICKME)) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else if (!is_same && (ci->flags & CI_PEACE) + && (get_access(u2, ci) >= get_access(u, ci))) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); +#if defined (IRC_ULTIMATE) || defined (IRC_ULTIMATE3) + } else if (is_protected(u2)) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); +#endif + } else { + char *av[3]; + + if ((ci->flags & CI_SIGNKICK) + || ((ci->flags & CI_SIGNKICK_LEVEL) + && !check_access(u, ci, CA_SIGNKICK))) + send_cmd(whosends(ci), "KICK %s %s :%s (%s)", ci->name, params, + reason, u->nick); + else + send_cmd(whosends(ci), "KICK %s %s :%s", ci->name, params, + reason); + av[0] = ci->name; + av[1] = params; + av[2] = reason; + do_kick(s_ChanServ, 3, av); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_ban(User * u) +{ + char *chan = strtok(NULL, " "); + char *params = strtok(NULL, " "); + char *reason = strtok(NULL, ""); + + Channel *c; + ChannelInfo *ci; + User *u2; + + int is_same; + + if (!reason) { + reason = "Requested"; + } else { + if (strlen(reason) > 200) + reason[200] = '\0'; + } + + if (!chan) { + struct u_chanlist *uc, *next; + + /* Bans the user on every channels he is on. */ + + for (uc = u->chans; uc; uc = next) { + next = uc->next; + if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN) + && check_access(u, ci, CA_BANME)) { + char *av[3]; + char mask[BUFSIZE]; + +#if defined (IRC_ULTIMATE) || defined(IRC_ULTIMATE3) || defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_HYBRID) + /* + * Dont ban/kick the user on channels where he is excepted + * to prevent services <-> server wars. + */ + if (is_excepted(ci, u)) + notice_lang(s_ChanServ, u, CHAN_EXCEPTED, + u->nick, ci->name); + continue; +#endif + av[0] = sstrdup("+b"); + get_idealban(ci, u, mask, sizeof(mask)); + av[1] = mask; + send_cmd(whosends(ci), "MODE %s +b %s", uc->chan->name, + av[1]); + chan_set_modes(s_ChanServ, uc->chan, 2, av, 1); + free(av[0]); + + if ((ci->flags & CI_SIGNKICK) + || ((ci->flags & CI_SIGNKICK_LEVEL) + && !check_access(u, ci, CA_SIGNKICK))) + send_cmd(whosends(ci), "KICK %s %s :%s (%s)", ci->name, + u->nick, reason, u->nick); + else + send_cmd(whosends(ci), "KICK %s %s :%s", ci->name, + u->nick, reason); + av[0] = ci->name; + av[1] = u->nick; + av[2] = reason; + do_kick(s_ChanServ, 3, av); + } + } + + return MOD_CONT; + } else if (!params) { + params = u->nick; + } + + is_same = (params == u->nick) ? 1 : (stricmp(params, u->nick) == 0); + + if (!(c = findchan(chan))) { + notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan); + } 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 (is_same ? !(u2 = u) : !(u2 = finduser(params))) { + notice_lang(s_ChanServ, u, NICK_X_NOT_IN_USE, params); + } else if (!is_same ? !check_access(u, ci, CA_BAN) : + !check_access(u, ci, CA_BANME)) { + notice_lang(s_ChanServ, u, ACCESS_DENIED); + } else if (!is_same && (ci->flags & CI_PEACE) + && (get_access(u2, ci) >= get_access(u, ci))) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); +#if defined (IRC_ULTIMATE) || defined(IRC_ULTIMATE3) || defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_HYBRID) + /* + * Dont ban/kick the user on channels where he is excepted + * to prevent services <-> server wars. + */ + } else if (is_excepted(ci, u2)) { + notice_lang(s_ChanServ, u, CHAN_EXCEPTED, u2->nick, ci->name); +#endif + } else { + char *av[3]; + char mask[BUFSIZE]; + + av[0] = sstrdup("+b"); + get_idealban(ci, u2, mask, sizeof(mask)); + av[1] = mask; + send_cmd(whosends(ci), "MODE %s +b %s", c->name, av[1]); + chan_set_modes(s_ChanServ, c, 2, av, 1); + free(av[0]); + + /* We still allow host banning while not allowing to kick */ + if (!is_on_chan(c, u2)) + return MOD_CONT; + + if ((ci->flags & CI_SIGNKICK) + || ((ci->flags & CI_SIGNKICK_LEVEL) + && !check_access(u, ci, CA_SIGNKICK))) + send_cmd(whosends(ci), "KICK %s %s :%s (%s)", ci->name, params, + reason, u->nick); + else + send_cmd(whosends(ci), "KICK %s %s :%s", ci->name, params, + reason); + av[0] = ci->name; + av[1] = params; + av[2] = reason; + do_kick(s_ChanServ, 3, av); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_cs_topic(User * u) +{ + char *chan = strtok(NULL, " "); + char *topic = strtok(NULL, ""); + + Channel *c; + ChannelInfo *ci; + + if (!chan) { + syntax_error(s_ChanServ, u, "TOPIC", CHAN_TOPIC_SYNTAX); + } else if (!(c = findchan(chan))) { + notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan); + } else if (!(ci = c->ci)) { + notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, c->name); + } else if (ci->flags & CI_VERBOTEN) { + notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, ci->name); + } else if (!is_services_admin(u) && !check_access(u, ci, CA_TOPIC)) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + } else { + if (ci->last_topic) + free(ci->last_topic); + ci->last_topic = topic ? sstrdup(topic) : NULL; + strscpy(ci->last_topic_setter, u->nick, NICKMAX); + ci->last_topic_time = time(NULL); + + if (c->topic) + free(c->topic); + c->topic = topic ? sstrdup(topic) : NULL; + strscpy(c->topic_setter, u->nick, NICKMAX); +#if defined(IRC_DREAMFORGE) && !defined(IRC_ULTIMATE) && !defined(IRC_UNREAL) + c->topic_time = c->topic_time - 1; +#else + c->topic_time = ci->last_topic_time; +#endif + + if (is_services_admin(u)) + alog("%s: %s!%s@%s changed topic of %s as services admin.", + s_ChanServ, u->nick, u->username, u->host, c->name); +#ifdef IRC_HYBRID + if (whosends(ci) == s_ChanServ) { + send_cmd(NULL, "SJOIN %ld %s + :%s", time(NULL), c->name, + s_ChanServ); + send_cmd(NULL, "MODE %s +o %s", c->name, s_ChanServ); + } + send_cmd(whosends(ci), "TOPIC %s :%s", c->name, + c->topic ? c->topic : ""); + if (whosends(ci) == s_ChanServ) { + send_cmd(s_ChanServ, "PART %s", c->name); + } +#else + send_cmd(whosends(ci), "TOPIC %s %s %lu :%s", c->name, u->nick, + c->topic_time, topic ? topic : ""); +#endif + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int 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 (!(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 (!check_access(u, ci, CA_UNBAN)) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + } else { +#ifndef IRC_BAHAMUT + int i; + char *av[3]; + /* Save original ban info */ + int count = c->bancount; + char **bans = scalloc(sizeof(char *) * count, 1); + memcpy(bans, c->bans, sizeof(char *) * count); + + av[0] = chan; + av[1] = sstrdup("-b"); + for (i = 0; i < count; i++) { + if (match_usermask(bans[i], u)) { + send_cmd(whosends(ci), "MODE %s -b %s", chan, bans[i]); + av[2] = sstrdup(bans[i]); + do_cmode(s_ChanServ, 3, av); + free(av[2]); + } + } + free(av[1]); + free(bans); +#else + send_cmd(ServerName, "SVSMODE %s -b %s", chan, u->nick); +#endif + notice_lang(s_ChanServ, u, CHAN_UNBANNED, chan); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int 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 (!(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) { + char *av[3]; + int i; + + /* Save original ban info */ + int count = c->bancount; + char **bans = scalloc(sizeof(char *) * count, 1); + for (i = 0; i < count; i++) + bans[i] = sstrdup(c->bans[i]); + + for (i = 0; i < count; i++) { + av[0] = sstrdup(chan); + av[1] = sstrdup("-b"); + av[2] = bans[i]; + send_cmd(whosends(ci), "MODE %s %s :%s", av[0], av[1], av[2]); + do_cmode(s_ChanServ, 3, av); + free(av[2]); + free(av[1]); + free(av[0]); + } + notice_lang(s_ChanServ, u, CHAN_CLEARED_BANS, chan); + free(bans); +#if defined (IRC_ULTIMATE) || defined (IRC_UNREAL) || defined (IRC_ULTIMATE3) || defined(IRC_HYBRID) + } else if (stricmp(what, "excepts") == 0) { + char *av[3]; + int i; + + /* Save original except info */ + int count = c->exceptcount; + char **excepts = scalloc(sizeof(char *) * count, 1); + for (i = 0; i < count; i++) + excepts[i] = sstrdup(c->excepts[i]); + + for (i = 0; i < count; i++) { + av[0] = sstrdup(chan); + av[1] = sstrdup("-e"); + av[2] = excepts[i]; + send_cmd(whosends(ci), "MODE %s %s :%s", av[0], av[1], av[2]); + do_cmode(s_ChanServ, 3, av); + free(av[2]); + free(av[1]); + free(av[0]); + } + notice_lang(s_ChanServ, u, CHAN_CLEARED_EXCEPTS, chan); + free(excepts); +#endif + } else if (stricmp(what, "modes") == 0) { + char buf[BUFSIZE], *end = buf; + char *argv[2]; + + if (c->mode) { + /* Clear modes */ + send_cmd(s_ChanServ, "MODE %s %s %s", c->name, MODESTOREMOVE, + c->key ? c->key : ""); + argv[0] = sstrdup(MODESTOREMOVE); + argv[1] = c->key ? c->key : NULL; + chan_set_modes(s_OperServ, c, c->key ? 2 : 1, argv, 0); + free(argv[0]); + check_modes(c); + } + + /* TODO: decide if the above implementation is better than this one. */ + + if (0) { + CBModeInfo *cbmi = cbmodeinfos; + CBMode *cbm; + + do { + if (c->mode & cbmi->flag) + *end++ = cbmi->mode; + } while ((++cbmi)->flag != 0); + + cbmi = cbmodeinfos; + + do { + if (cbmi->getvalue && (c->mode & cbmi->flag) + && !(cbmi->flags & CBM_MINUS_NO_ARG)) { + char *value = cbmi->getvalue(c); + + if (value) { + *end++ = ' '; + while (*value) + *end++ = *value++; + + /* Free the value */ + cbm = &cbmodes[(int) cbmi->mode]; + cbm->setvalue(c, NULL); + } + } + } while ((++cbmi)->flag != 0); + + *end = 0; + + send_cmd(whosends(ci), "MODE %s -%s", c->name, buf); + c->mode = 0; + check_modes(c); + } + notice_lang(s_ChanServ, u, CHAN_CLEARED_MODES, chan); + } else if (stricmp(what, "ops") == 0) { + char *av[3]; + struct c_userlist *cu, *next; + + for (cu = c->users; cu; cu = next) { + next = cu->next; + if (!chan_has_user_status(c, cu->user, CUS_OP)) + continue; + av[0] = sstrdup(chan); + av[1] = sstrdup("-o"); + av[2] = sstrdup(cu->user->nick); + send_cmd(whosends(ci), "MODE %s %s :%s", av[0], av[1], av[2]); + do_cmode(s_ChanServ, 3, av); + free(av[2]); + free(av[1]); + free(av[0]); + } + notice_lang(s_ChanServ, u, CHAN_CLEARED_OPS, chan); + } else if (stricmp(what, "voices") == 0) { + char *av[3]; + struct c_userlist *cu, *next; + + for (cu = c->users; cu; cu = next) { + next = cu->next; + if (!chan_has_user_status(c, cu->user, CUS_VOICE)) + continue; + av[0] = sstrdup(chan); + av[1] = sstrdup("-v"); + av[2] = sstrdup(cu->user->nick); + send_cmd(whosends(ci), "MODE %s %s :%s", av[0], av[1], av[2]); + do_cmode(s_ChanServ, 3, av); + free(av[2]); + free(av[1]); + free(av[0]); + } + notice_lang(s_ChanServ, u, CHAN_CLEARED_VOICES, chan); + } else if (stricmp(what, "users") == 0) { + char *av[3]; + struct c_userlist *cu, *next; + char buf[256]; + + snprintf(buf, sizeof(buf), "CLEAR USERS command from %s", u->nick); + + for (cu = c->users; cu; cu = next) { + next = cu->next; + av[0] = sstrdup(chan); + av[1] = sstrdup(cu->user->nick); + av[2] = sstrdup(buf); + send_cmd(whosends(ci), "KICK %s %s :%s", av[0], av[1], av[2]); + do_kick(s_ChanServ, 3, av); + free(av[2]); + free(av[1]); + free(av[0]); + } + notice_lang(s_ChanServ, u, CHAN_CLEARED_USERS, chan); + } else { + syntax_error(s_ChanServ, u, "CLEAR", CHAN_CLEAR_SYNTAX); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_getkey(User * u) +{ + char *chan = strtok(NULL, " "); + ChannelInfo *ci; + + if (chan && (ci = cs_findchan(chan)) && !(ci->flags & CI_VERBOTEN) + && check_access(u, ci, CA_GETKEY)) { + notice_user(s_ChanServ, u, "KEY %s %s", ci->name, + (ci-> + c ? (ci->c->key ? ci->c->key : "NO KEY") : "NO KEY")); + } else { + notice_user(s_ChanServ, u, "KEY %s ERROR", chan); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_getpass(User * u) +{ +#ifndef USE_ENCRYPTION + char *chan = strtok(NULL, " "); + ChannelInfo *ci; +#endif + + /* Assumes that permission checking has already been done. */ +#ifdef USE_ENCRYPTION + notice_lang(s_ChanServ, u, CHAN_GETPASS_UNAVAILABLE); +#else + 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 if (CSRestrictGetPass && !is_services_root(u)) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + } else { + alog("%s: %s!%s@%s used GETPASS on %s", + s_ChanServ, u->nick, u->username, GetHost(u), 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 + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_sendpass(User * u) +{ +#ifndef USE_ENCRYPTION + char *chan = strtok(NULL, " "); + ChannelInfo *ci; + NickCore *founder; +#endif + +#ifdef USE_ENCRYPTION + notice_lang(s_ChanServ, u, CHAN_SENDPASS_UNAVAILABLE); +#else + if (!chan) { + syntax_error(s_ChanServ, u, "SENDPASS", CHAN_SENDPASS_SYNTAX); + } else if (RestrictMail && !is_oper(u)) { + notice_lang(s_ChanServ, u, PERMISSION_DENIED); + } else if (!(ci = cs_findchan(chan)) || !(founder = ci->founder)) { + 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 { + char buf[BUFSIZE]; + MailInfo *mail; + + snprintf(buf, sizeof(buf), + getstring2(founder, CHAN_SENDPASS_SUBJECT), ci->name); + mail = MailBegin(u, founder, buf, s_ChanServ); + if (!mail) + return MOD_CONT; + + fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_HEAD)); + fprintf(mail->pipe, "\n\n"); + fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_1), + ci->name); + fprintf(mail->pipe, "\n\n"); + fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_2), + ci->founderpass); + fprintf(mail->pipe, "\n\n"); + fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_3)); + fprintf(mail->pipe, "\n\n"); + fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_4)); + fprintf(mail->pipe, "\n\n"); + fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_5), + NetworkName); + fprintf(mail->pipe, "\n.\n"); + + MailEnd(mail); + + alog("%s: %s!%s@%s used SENDPASS on %s", s_ChanServ, u->nick, + u->username, GetHost(u), chan); + notice_lang(s_ChanServ, u, CHAN_SENDPASS_OK, chan); + } +#endif + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_forbid(User * u) +{ + ChannelInfo *ci; + char *chan = strtok(NULL, " "); + char *reason = strtok(NULL, ""); + + Channel *c; + + /* Assumes that permission checking has already been done. */ + if (!chan || (ForceForbidReason && !reason)) { + syntax_error(s_ChanServ, u, "FORBID", + (ForceForbidReason ? CHAN_FORBID_SYNTAX_REASON : + CHAN_FORBID_SYNTAX)); + return MOD_CONT; + } + if (readonly) + notice_lang(s_ChanServ, u, READ_ONLY_MODE); + if ((ci = cs_findchan(chan)) != NULL) + delchan(ci); + ci = makechan(chan); + if (ci) { + ci->flags |= CI_VERBOTEN; + ci->forbidby = sstrdup(u->nick); + if (reason) + ci->forbidreason = sstrdup(reason); + + if ((c = findchan(ci->name))) { + struct c_userlist *cu, *next; + char *av[3]; + + for (cu = c->users; cu; cu = next) { + next = cu->next; + + if (is_oper(cu->user)) + continue; + + av[0] = c->name; + av[1] = cu->user->nick; + av[2] = reason ? reason : "CHAN_FORBID_REASON"; + send_cmd(s_ChanServ, "KICK %s %s :%s", av[0], av[1], + av[2]); + do_kick(s_ChanServ, 3, av); + } + } + + if (WallForbid) + wallops(s_ChanServ, "\2%s\2 used FORBID on channel \2%s\2", + u->nick, ci->name); + + alog("%s: %s set FORBID for channel %s", s_ChanServ, u->nick, + ci->name); + notice_lang(s_ChanServ, u, CHAN_FORBID_SUCCEEDED, chan); + } else { + alog("%s: Valid FORBID for %s by %s failed", s_ChanServ, ci->name, + u->nick); + notice_lang(s_ChanServ, u, CHAN_FORBID_FAILED, chan); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_suspend(User * u) +{ + ChannelInfo *ci; + char *chan = strtok(NULL, " "); + char *reason = strtok(NULL, ""); + + Channel *c; + + /* Assumes that permission checking has already been done. */ + if (!chan || (ForceForbidReason && !reason)) { + syntax_error(s_ChanServ, u, "SUSPEND", + (ForceForbidReason ? CHAN_SUSPEND_SYNTAX_REASON : + CHAN_SUSPEND_SYNTAX)); + return MOD_CONT; + } + if (readonly) + notice_lang(s_ChanServ, u, READ_ONLY_MODE); + if ((ci = cs_findchan(chan)) == NULL) + ci = makechan(chan); + if (ci) { + ci->flags |= CI_SUSPENDED; + ci->forbidby = sstrdup(u->nick); + if (reason) + ci->forbidreason = sstrdup(reason); + + if ((c = findchan(ci->name))) { + struct c_userlist *cu, *next; + char *av[3]; + + for (cu = c->users; cu; cu = next) { + next = cu->next; + + if (is_oper(cu->user)) + continue; + + av[0] = c->name; + av[1] = cu->user->nick; + av[2] = reason ? reason : "CHAN_SUSPEND_REASON"; + send_cmd(s_ChanServ, "KICK %s %s :%s", av[0], av[1], + av[2]); + do_kick(s_ChanServ, 3, av); + } + } + + if (WallForbid) + wallops(s_ChanServ, "\2%s\2 used SUSPEND on channel \2%s\2", + u->nick, ci->name); + + alog("%s: %s set SUSPEND for channel %s", s_ChanServ, u->nick, + ci->name); + notice_lang(s_ChanServ, u, CHAN_SUSPEND_SUCCEEDED, chan); + } else { + alog("%s: Valid SUSPEND for %s by %s failed", s_ChanServ, ci->name, + u->nick); + notice_lang(s_ChanServ, u, CHAN_SUSPEND_FAILED, chan); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_unsuspend(User * u) +{ + ChannelInfo *ci; + char *chan = strtok(NULL, " "); + + /* Assumes that permission checking has already been done. */ + if (!chan) { + syntax_error(s_ChanServ, u, "UNSUSPEND", CHAN_UNSUSPEND_SYNTAX); + return MOD_CONT; + } + if (chan[0] != '#') { + notice_lang(s_ChanServ, u, CHAN_UNSUSPEND_ERROR); + return MOD_CONT; + } + if (readonly) + notice_lang(s_ChanServ, u, READ_ONLY_MODE); + + ci = cs_findchan(chan); + + if (ci) { + ci->flags &= ~CI_SUSPENDED; + ci->forbidreason = NULL; + ci->forbidby = NULL; + + if (WallForbid) + wallops(s_ChanServ, "\2%s\2 used UNSUSPEND on channel \2%s\2", + u->nick, ci->name); + + alog("%s: %s set UNSUSPEND for channel %s", s_ChanServ, u->nick, + ci->name); + notice_lang(s_ChanServ, u, CHAN_UNSUSPEND_SUCCEEDED, chan); + } else { + alog("%s: Valid UNSUSPEND for %s by %s failed", s_ChanServ, + chan, u->nick); + notice_lang(s_ChanServ, u, CHAN_UNSUSPEND_FAILED, chan); + } + return MOD_CONT; +} + +/*************************************************************************/ + +static int do_status(User * u) +{ + ChannelInfo *ci; + User *u2; + char *nick, *chan; + + chan = strtok(NULL, " "); + nick = strtok(NULL, " "); + if (!nick || strtok(NULL, " ")) { + notice_user(s_ChanServ, u, "STATUS ERROR Syntax error"); + return MOD_CONT; + } + if (!(ci = cs_findchan(chan))) { + char *temp = chan; + chan = nick; + nick = temp; + ci = cs_findchan(chan); + } + if (!ci) { + notice_user(s_ChanServ, u, + "STATUS ERROR Channel %s not registered", chan); + } else if (ci->flags & CI_VERBOTEN) { + notice_user(s_ChanServ, u, "STATUS ERROR Channel %s forbidden", + chan); + return MOD_CONT; + } else if ((u2 = finduser(nick)) != NULL) { + notice_user(s_ChanServ, u, "STATUS %s %s %d", chan, nick, + get_access(u2, ci)); + } else { /* !u2 */ + notice_user(s_ChanServ, u, "STATUS ERROR Nick %s not online", + nick); + } + return MOD_CONT; +} + +/*************************************************************************/ |