/* S-line module.
*
* IRC Services is copyright (c) 1996-2007 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file COPYING for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "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;
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1