/* S-line module. * * IRC Services is copyright (c) 1996-2007 Andrew Church. * E-mail: * Parts written by Andrew Kempe and others. * This program is free but copyrighted software; see the file COPYING for * details. */ #include "services.h" #include "modules.h" #include "conffile.h" #include "commands.h" #include "language.h" #include "operserv.h" #define NEED_MAKE_REASON #include "maskdata.h" #include "sline.h" /*************************************************************************/ static Module *module; static Module *module_operserv; static Module *module_nickserv; static int cb_send_sgline = -1; static int cb_send_sqline = -1; static int cb_send_szline = -1; static int cb_cancel_sgline = -1; static int cb_cancel_sqline = -1; static int cb_cancel_szline = -1; static char * SlineDBName; static char * SGlineReason; static char * SQlineReason; static char * SZlineReason; static int ImmediatelySendSline; static time_t SGlineExpiry; static time_t SQlineExpiry; static time_t SZlineExpiry; static int WallOSSline; static int WallSlineExpire; static int SQlineIgnoreOpers; static int SQlineKill; static int db_opened = 0; /* Set nonzero if we don't have client IP info: * -1 = no IP info and no SZLINE-like command either * +1 = no IP info, but SZLINE command available */ static int no_szline = 0; static void do_sgline(User *u); static void do_sqline(User *u); static void do_szline(User *u); static uint8 sline_types[3] = {MD_SGLINE, MD_SQLINE, MD_SZLINE}; static Command cmds[] = { {"SGLINE", do_sgline, is_services_oper, OPER_HELP_SGLINE, -1,-1}, {"SQLINE", do_sqline, is_services_oper, OPER_HELP_SQLINE, -1,-1}, {"SZLINE", do_szline, is_services_oper, OPER_HELP_SZLINE, -1,-1}, { NULL } }; /*************************************************************************/ /************************** Internal functions ***************************/ /*************************************************************************/ /* Callback for saving data. */ static int do_save_data(void) { sync_sline_db(SlineDBName); return 0; } /*************************************************************************/ /* Send an S-line to the uplink server. */ static void send_sline(uint8 type, const MaskData *sline) { int cb; const char *reason; if (type == MD_SGLINE) { cb = cb_send_sgline; reason = SGlineReason; } else if (type == MD_SQLINE && !SQlineKill) { cb = cb_send_sqline; reason = SQlineReason; } else if (type == MD_SZLINE) { cb = cb_send_szline; reason = SZlineReason; } else { return; } call_callback_4(module, cb, sline->mask, sline->expires, sline->who, make_reason(reason, sline)); } /*************************************************************************/ /* Remove an S-line from the uplink server. */ static void cancel_sline(uint8 type, char *mask) { int cb; if (type == MD_SGLINE) { cb = cb_cancel_sgline; } else if (type == MD_SQLINE) { cb = cb_cancel_sqline; } else if (type == MD_SZLINE) { cb = cb_cancel_szline; } else { return; } call_callback_1(module, cb, mask); } /*************************************************************************/ /* SQline checker, shared by check_sline() and check_sqline_nickchange(). * Returns the reason string to be used if the user is to be killed, else * NULL. */ static char *check_sqline(const char *nick) { User *u; MaskData *sline; if (SQlineIgnoreOpers && (u = get_user(nick)) && is_oper(u)) return NULL; sline = get_matching_maskdata(MD_SQLINE, nick); if (sline) { char *retval = NULL; /* Don't kill/nickchange users if they just got changed to a guest * nick */ if (!is_guest_nick(nick)) { char *reason = make_reason(SQlineReason, sline); if (!SQlineKill && (protocol_features & PF_CHANGENICK)) { send_cmd(ServerName, "432 %s %s Invalid nickname (%s)", nick, nick, reason); send_nickchange_remote(nick, make_guest_nick()); } else { /* User is to be killed */ retval = reason; } } send_sline(MD_SQLINE, sline); time(&sline->lastused); put_maskdata(MD_SQLINE, sline); return retval; } return NULL; } /*************************************************************************/ /************************** Callback functions ***************************/ /*************************************************************************/ /* Does the user match any S-lines? Return 1 (and kill the user) if so, * else 0. Note that if a Q:line (and no G:line or Z:line) is matched, and * SQlineKill is not set, we send a 432 (erroneous nickname) reply to the * client and change their nick if the ircd supports forced nick changing. */ static int check_sline(int ac, char **av) { const char *nick = av[0], *name = av[6], *ip = (ac>=9 ? av[8] : NULL); MaskData *sline; char *reason; if (noakill) return 0; if (ip) { sline = get_matching_maskdata(MD_SZLINE, ip); if (sline) { send_cmd(s_OperServ, "KILL %s :%s (%s)", nick, s_OperServ, make_reason(SZlineReason, sline)); send_sline(MD_SZLINE, sline); time(&sline->lastused); put_maskdata(MD_SZLINE, sline); return 1; } } else { if (!no_szline) { if (protocol_features & PF_SZLINE) { if (!ImmediatelySendSline) { wallops(s_OperServ, "\2WARNING\2: Client IP addresses are not" " available with this IRC server; SZLINEs" " cannot be used unless ImmediatelySendSline" " is enabled in %s.", MODULES_CONF); no_szline = -1; } else { no_szline = 1; } } else { wallops(s_OperServ, "\2WARNING:\2 Client IP addresses are not available" " with this IRC server; SZLINEs cannot be used."); no_szline = -1; } module_log("warning: client IP addresses not available with" " this IRC server"); } } sline = get_matching_maskdata(MD_SGLINE, name); if (sline) { /* Don't use kill_user(); that's for people who have already signed * on. This is called before the User structure is created. */ send_cmd(s_OperServ, "KILL %s :%s (%s)", nick, s_OperServ, make_reason(SGlineReason, sline)); send_sline(MD_SGLINE, sline); time(&sline->lastused); put_maskdata(MD_SGLINE, sline); return 1; } reason = check_sqline(nick); if (reason) { send_cmd(s_OperServ, "KILL %s :%s (%s)", nick, s_OperServ, reason); return 1; } return 0; } /*************************************************************************/ /* Does the user (who has just changed their nick) match any SQlines? If * so, send them a 432 and, on servers supporting forced nick changing, * change their nick (if !SQlineKill). */ static int check_sqline_nickchange(User *u, const char *oldnick) { char *reason = check_sqline(u->nick); if (reason) { kill_user(s_OperServ, u->nick, reason); return 1; } return 0; } /*************************************************************************/ /* Callback for NickServ REGISTER/LINK check; we disallow * registration/linking of SQlined nicknames. */ static int do_reglink_check(const User *u, const char *nick, const char *pass, const char *email) { return get_matching_maskdata(MD_SQLINE, nick) != NULL; } /*************************************************************************/ /************************** S-line list editing **************************/ /*************************************************************************/ /* Note that all string parameters are assumed to be non-NULL; expiry must * be set to the time when the S-line should expire (0 for none). Mask * is converted to lowercase on return. */ EXPORT_FUNC(create_sline) void create_sline(uint8 type, char *mask, const char *reason, const char *who, time_t expiry) { MaskData *sline; strlower(mask); if (maskdata_count(type) >= MAX_MASKDATA) { module_log("Attempt to add S%cLINE to full list!", type); return; } sline = scalloc(1, sizeof(*sline)); sline->mask = sstrdup(mask); sline->reason = sstrdup(reason); sline->time = time(NULL); sline->expires = expiry; strscpy(sline->who, who, NICKMAX); sline = add_maskdata(type, sline); if (ImmediatelySendSline) send_sline(type, sline); } /*************************************************************************/ /* Handle an OperServ S*LINE command. */ static void do_sline(uint8 type, User *u); static void do_sgline(User *u) { do_sline(MD_SGLINE, u); } static void do_sqline(User *u) { do_sline(MD_SQLINE, u); } static void do_szline(User *u) { if (no_szline < 0) notice_lang(s_OperServ, u, OPER_SZLINE_NOT_AVAIL); else do_sline(MD_SZLINE, u); } static int check_add_sline(User *u, uint8 type, char *mask, time_t *expiry_ptr); static void do_add_sline(User *u, uint8 type, MaskData *md); static void do_del_sline(User *u, uint8 type, MaskData *md); static MaskDataCmdInfo sline_cmd_info = { "SxLINE", /* command name (overwritten later) */ 0, /* MaskData type (overwritten later) */ NULL, /* default expiry time pointer (overwritten later) */ OPER_SLINE_SYNTAX, OPER_SLINE_ADD_SYNTAX, OPER_SLINE_DEL_SYNTAX, OPER_TOO_MANY_SLINES, OPER_SLINE_EXISTS, OPER_SLINE_ADDED, OPER_SLINE_NOT_FOUND, OPER_SLINE_REMOVED, OPER_SLINE_LIST_HEADER, OPER_SLINE_LIST_FORMAT, OPER_SLINE_VIEW_FORMAT, OPER_SLINE_VIEW_UNUSED_FORMAT, OPER_SLINE_COUNT, NULL, /* function to mangle masks for add/delete */ check_add_sline, /* function to check validity of mask on add */ do_add_sline, /* function to call on mask addition */ do_del_sline, /* function to call on mask removal */ NULL, /* function to call for unknown commands */ }; static void do_sline(uint8 type, User *u) { char sxline[7]; sprintf(sxline, "S%cLINE", type); sline_cmd_info.name = sxline; sline_cmd_info.md_type = type; switch (type) { case MD_SGLINE: sline_cmd_info.def_expiry_ptr = &SGlineExpiry; break; case MD_SQLINE: sline_cmd_info.def_expiry_ptr = &SQlineExpiry; break; case MD_SZLINE: sline_cmd_info.def_expiry_ptr = &SZlineExpiry; break; default: module_log("do_sline(): bad type value (%u)", type); notice_lang(s_OperServ, u, INTERNAL_ERROR); return; } do_maskdata_cmd(&sline_cmd_info, u); } /*************************************************************************/ static int check_add_sline(User *u, uint8 type, char *mask, time_t *expiry_ptr) { char *t; #ifdef CLEAN_COMPILE expiry_ptr = expiry_ptr; #endif /* Make sure mask is not too general. */ if (strchr(mask,'*') != NULL && mask[strspn(mask,"*?")] == 0 && ((t = strchr(mask,'?')) == NULL || strchr(t+1,'?') == NULL) ) { char cmdname[7]; snprintf(cmdname, sizeof(cmdname), "S%cLINE", (char)type); notice_lang(s_OperServ, u, OPER_SLINE_MASK_TOO_GENERAL, cmdname); return 0; } return 1; } /*************************************************************************/ static void do_add_sline(User *u, uint8 type, MaskData *md) { if (WallOSSline) { char buf[128]; expires_in_lang(buf, sizeof(buf), NULL, md->expires); wallops(s_OperServ, "%s added an S%cLINE for \2%s\2 (%s)", u->nick, type, md->mask, buf); } if (ImmediatelySendSline) send_sline(type, md); } /*************************************************************************/ static void do_del_sline(User *u, uint8 type, MaskData *md) { cancel_sline(type, md->mask); } /*************************************************************************/ /******************************* Callbacks *******************************/ /*************************************************************************/ /* Callback on connection to uplink server. */ static int do_connect(void) { if (ImmediatelySendSline) { MaskData *sline; static uint8 types[] = {MD_SGLINE, MD_SQLINE, MD_SZLINE}; int i; for (i = 0; i < lenof(types); i++) { for (sline = first_maskdata(types[i]); sline; sline = next_maskdata(types[i]) ) { send_sline(types[i], sline); } } } return 0; } /*************************************************************************/ /* Callback for S-line expiration. */ static int do_expire_maskdata(uint32 type, MaskData *md) { int i; for (i = 0; i < lenof(sline_types); i++) { if (type == sline_types[i]) { if (WallSlineExpire) wallops(s_OperServ, "S%cLINE on %s has expired", sline_types[i], md->mask); cancel_sline((uint8)type, md->mask); } } return 0; } /*************************************************************************/ /* OperServ HELP callback, to handle HELP SQLINE (complex). */ static int do_help(User *u, char *param) { /* param should always be non-NULL here, but let's be paranoid */ if (param && stricmp(param,"SQLINE") == 0) { notice_help(s_OperServ, u, OPER_HELP_SQLINE); if (SQlineKill) notice_help(s_OperServ, u, OPER_HELP_SQLINE_KILL); else notice_help(s_OperServ, u, OPER_HELP_SQLINE_NOKILL); if (SQlineIgnoreOpers) notice_help(s_OperServ, u, OPER_HELP_SQLINE_IGNOREOPERS); notice_help(s_OperServ, u, OPER_HELP_SQLINE_END); return 1; } return 0; } /*************************************************************************/ /***************************** Module stuff ******************************/ /*************************************************************************/ const int32 module_version = MODULE_VERSION_CODE; ConfigDirective module_config[] = { { "ImmediatelySendSline",{{CD_SET, 0, &ImmediatelySendSline } } }, { "SGlineExpiry", { { CD_TIME, 0, &SGlineExpiry } } }, { "SGlineReason", { { CD_STRING, CF_DIRREQ, &SGlineReason } } }, { "SQlineExpiry", { { CD_TIME, 0, &SGlineExpiry } } }, { "SQlineIgnoreOpers",{ { CD_SET, 0, &SQlineIgnoreOpers } } }, { "SQlineKill", { { CD_SET, 0, &SQlineKill } } }, { "SQlineReason", { { CD_STRING, CF_DIRREQ, &SQlineReason } } }, { "SZlineExpiry", { { CD_TIME, 0, &SGlineExpiry } } }, { "SZlineReason", { { CD_STRING, CF_DIRREQ, &SZlineReason } } }, { "SlineDB", { { CD_STRING, CF_DIRREQ, &SlineDBName } } }, { "WallSlineExpire", { { CD_SET, 0, &WallSlineExpire } } }, { "WallOSSline", { { CD_SET, 0, &WallOSSline } } }, { NULL } }; /*************************************************************************/ static int do_load_module(Module *mod, const char *modname) { if (strcmp(modname, "nickserv/main") == 0) { module_nickserv = mod; if (!add_callback(mod, "REGISTER/LINK check", do_reglink_check)) module_log("Unable to register NickServ REGISTER/LINK check" " callback"); } return 0; } /*************************************************************************/ static int do_unload_module(Module *mod) { if (mod == module_nickserv) { remove_callback(module_nickserv, "REGISTER/LINK check", do_reglink_check); module_nickserv = NULL; } return 0; } /*************************************************************************/ int init_module(Module *module_) { module = module_; module_operserv = find_module("operserv/main"); if (!module_operserv) { module_log("Main OperServ module not loaded"); return 0; } use_module(module_operserv); if (!register_commands(module_operserv, cmds)) { module_log("Unable to register commands"); exit_module(0); return 0; } cb_send_sgline = register_callback(module, "send_sgline"); cb_send_sqline = register_callback(module, "send_sqline"); cb_send_szline = register_callback(module, "send_szline"); cb_cancel_sgline = register_callback(module, "cancel_sgline"); cb_cancel_sqline = register_callback(module, "cancel_sqline"); cb_cancel_szline = register_callback(module, "cancel_szline"); if (cb_send_sgline < 0 || cb_send_sqline < 0 || cb_send_szline < 0 || cb_cancel_sgline < 0 || cb_cancel_sqline < 0 || cb_cancel_szline < 0 ) { module_log("Unable to register callbacks"); exit_module(0); return 0; } if (!add_callback(NULL, "load module", do_load_module) || !add_callback(NULL, "unload module", do_unload_module) || !add_callback(NULL, "connect", do_connect) || !add_callback(NULL, "user check", check_sline) || !add_callback(NULL, "user nickchange (after)", check_sqline_nickchange) || !add_callback(NULL, "save data", do_save_data) || !add_callback(module_operserv, "expire maskdata", do_expire_maskdata) || !add_callback(module_operserv, "HELP", do_help) ) { module_log("Unable to add callbacks"); exit_module(0); return 0; } open_sline_db(SlineDBName); db_opened = 1; return 1; } /*************************************************************************/ int exit_module(int shutdown_unused) { #ifdef CLEAN_COMPILE shutdown_unused = shutdown_unused; #endif if (db_opened) close_sline_db(SlineDBName); if (module_nickserv) do_unload_module(module_nickserv); remove_callback(NULL, "save data", do_save_data); remove_callback(NULL, "user nickchange (after)", check_sqline_nickchange); remove_callback(NULL, "user check", check_sline); remove_callback(NULL, "connect", do_connect); remove_callback(NULL, "unload module", do_unload_module); remove_callback(NULL, "load module", do_load_module); unregister_callback(module, cb_cancel_szline); unregister_callback(module, cb_cancel_sqline); unregister_callback(module, cb_cancel_sgline); unregister_callback(module, cb_send_szline); unregister_callback(module, cb_send_sqline); unregister_callback(module, cb_send_sgline); if (module_operserv) { remove_callback(module_operserv, "HELP", do_help); remove_callback(module_operserv,"expire maskdata",do_expire_maskdata); unregister_commands(module_operserv, cmds); unuse_module(module_operserv); module_operserv = NULL; } return 1; } /*************************************************************************/