/******************************************************************
* PTlink Services is (C) CopyRight PTlink IRC Software 1999-2005 *
* http://software.pt-link.net *
* This program is distributed under GNU Public License *
* Please read the file COPYING for copyright information. *
******************************************************************
File: cs_akick.c
Description: chanserv akick command
* $Id: cs_akick.c,v 1.7 2005/10/18 16:25:06 jpinto Exp $
*/
#include "module.h"
#include "chanserv.h"
#include "chanrecord.h"
#include "my_sql.h"
#include "cs_role.h" /* We need P_AKICK */
#include "nsmacros.h"
#include "nickserv.h" /* Authenticated ? */
#include "dbconf.h"
/* lang files */
#include "lang/common.lh"
#include "lang/cscommon.lh"
#include "lang/cs_akick.lh"
SVS_Module mod_info =
/* module, version, description */
{"cs_akick", "3.2", "chanserv akick command" };
#define DB_VERSION 2
/* Change Log
3.2 - #73, crash on cs_akick del all on chan without akicks
3.1 - #53: CS AKICK DEL ALL
3.0 - 0000305: foreign keys for data integrity
Added cs_akick.2.sql changes
2.0 - 0000293: invalid sql string on cs_akick list with mask
- 0000282: akick mask is not on the standard nick!user@host format
- 0000265: remove nickserv cache system
- 0000281: No auth nicks can't use chanserv
- 0000280: expire date on akick list
*/
/* external functions we need */
ServiceUser* (*chanserv_suser)(void);
int e_regchan_join = 0;
int e_expire = 0;
MOD_REQUIRES
MOD_FUNC(dbconf_get)
MOD_FUNC(dbconf_get_or_build)
MOD_FUNC(e_expire) /* we need this to run the expire routines */
MOD_FUNC(role_with_permission)
MOD_FUNC(chanserv_suser)
MOD_FUNC(e_regchan_join)
MOD_END
/* internal functions */
int ev_cs_akick_chan_join(ChanRecord *cr, IRC_ChanNode* cn);
void load_akicks_for(ChanRecord* cr);
int find_akick(ChanRecord* cr, char *mask);
int mysql_insert_akick(u_int32_t scid, char* mask, char* reason, int duration, char *who);
int mysql_delete_akick(u_int32_t scid, char* mask);
char* match_akick(ChanRecord *cr, char *usermask);
void ev_cs_akick_timer_part(IRC_Chan* chan, int tag);
int ev_cs_akick_expire(void* dummy1, void* dummy2);
int akick_count(u_int32_t scid);
int sql_upgrade(int version, int post);
/* available commands from module */
void cs_akick(IRC_User *s, IRC_User *u);
/* Local settings */
int ChanServNeedsAuth = 0;
/* Local config */
static int MaxAkicksPerChan;
DBCONF_PROVIDES
DBCONF_INT(MaxAkicksPerChan, "20", "Max. number of of akicks per channel")
DBCONF_END
/* Remote config */
static int NeedsAuth;
DBCONF_REQUIRES
DBCONF_GET("chanserv", NeedsAuth)
DBCONF_END
/* this is called before load and at services rehash */
int mod_rehash(void)
{
if(dbconf_get(dbconf_requires) < 0)
{
errlog("Required configuration item is missing!");
return -1;
}
if(dbconf_get_or_build(mod_info.name, dbconf_provides) < 0 )
{
errlog("Error reading dbconf!");
return -1;
}
return 0;
}
/* Local variables */
ServiceUser* csu;
int cs_log = 0;
int mod_load(void)
{
int r;
r = sql_check_inst_upgrade(mod_info.name, DB_VERSION, sql_upgrade);
if(r == 1) /* module was installed */
{ /* lets add the permission to the admin role */
sql_execute("UPDATE cs_role SET perms=(perms | %d) "
"WHERE master_rid=0", P_AKICK);
}
if( r < 0 )
return -1;
cs_log = log_handle("chanserv");
csu = chanserv_suser();
/* Add action handlers */
mod_add_event_action(e_regchan_join, (ActionHandler) ev_cs_akick_chan_join);
mod_add_event_action(e_expire, (ActionHandler) ev_cs_akick_expire);
ev_cs_akick_expire(NULL, NULL);
/* Add command */
suser_add_cmd(csu, "AKICK", cs_akick, AKICK_SUMMARY, AKICK_HELP);
return 0;
}
void mod_unload(void)
{
suser_del_mod_cmds(csu, &mod_info);
}
/* s = service the command was sent to
u = user the command was sent from */
void cs_akick(IRC_User *s, IRC_User *u)
{
u_int32_t source_snid;
char *chname, *cmd;
ChanRecord *cr = NULL;
chname = strtok(NULL, " ");
cmd = strtok(NULL, " ");
/* status validation */
CHECK_IF_IDENTIFIED_NICK
/* base syntax validation */
if(ChanServNeedsAuth && !IsAuthenticated(u))
send_lang(u, s, NEEDS_AUTH_NICK);
else
if(IsNull(chname) || IsNull(cmd))
send_lang(u, s, CHAN_AKICK_SYNTAX);
else if((cr = OpenCR(chname)) == NULL)
send_lang(u, s, CHAN_X_NOT_REGISTERED, chname);
else
if(role_with_permission(cr->scid, source_snid, P_AKICK) == 0)
send_lang(u, s, NO_AKICK_PERM_ON_X, chname);
else if(strcasecmp(cmd, "ADD") == 0) /* Add akick */
{
char buf[NICKLEN+HOSTLEN+USERLEN+3];
char *mask, *reason;
char *nick = NULL, *user = NULL, *host = NULL;
int duration = 0;
buf[0] = '\0'; /* just to be safe */
mask = NULL;
mask = strtok(NULL, " ");
if(mask)
reason = strtok(NULL, "");
else
reason = NULL;
if(mask)
{
host = strchr(mask,'@');
if(host)
{
*host ='\0';
host++;
}
user = strchr(mask, '!');
if(user)
{
*user ='\0';
user++;
nick = mask;
}
else
{
if(host == NULL)
nick = mask;
else
user =mask;
}
if(host == NULL || (*host == '\0'))
host = "*";
if((user == NULL) || (*user == '\0'))
user = "*";
if((nick == NULL) || (*nick == '\0'))
nick = "*";
snprintf(buf, sizeof(buf), "%s!%s@%s", nick, user, host);
collapse(buf);
}
if(reason)
duration = strip_reason(&reason);
else
duration = 5*24*3600; /* default akick time is one week */
/* syntax validation */
if( IsNull(mask))
send_lang(u, s, CHAN_AKICK_ADD_SYNTAX);
/* privileges validation */
/* check requirements */
else if(MaxAkicksPerChan && akick_count(cr->scid) >= MaxAkicksPerChan)
send_lang(u, s, REACHED_MAX_AKICK_X, MaxAkicksPerChan);
else if(find_akick(cr, buf))
send_lang(u, s, ALREADY_AKICK_X_X, mask, cr->name);
else
{
if(mysql_insert_akick(cr->scid, buf, reason, duration, u->nick) > 0)
{
send_lang(u, s, ADDED_AKICK_X_X, buf, cr->name);
if(cr->extra[ED_AKICKS] == NULL) /* it may be the first akick */
{
cr->extra[ED_AKICKS] = malloc(sizeof(darray));
array_init(cr->extra[ED_AKICKS], 1, DA_STRING);
}
array_add_str(cr->extra[ED_AKICKS], buf);
}
else
send_lang(u, s, UPDATE_FAIL);
}
}
else if(strcasecmp(cmd, "DEL") == 0) /* Del akick */
{
int is_all = 0;
char *mask = strtok(NULL , " ");
if(mask && (strcasecmp(mask, "ALL") == 0))
is_all = 1;
/* syntax validation */
if(IsNull(mask))
send_lang(u, s, CHAN_AKICK_DEL_SYNTAX);
/* privileges validation */
/* check requirements */
else if(!is_all && (find_akick(cr, mask) == 0))
send_lang(u, s, AKICK_X_X_NOT_FOUND, mask, cr->name);
else
{
mysql_delete_akick(cr->scid, mask);
if(is_all)
{
send_lang(u, s, DELETED_AKICK_X_ALL, cr->name);
if(cr->extra[ED_AKICKS])
array_delall_str(cr->extra[ED_AKICKS]);
}
else
{
send_lang(u, s, DELETED_AKICK_X_X, mask, cr->name);
array_del_str(cr->extra[ED_AKICKS], mask);
}
}
}
else if(strcasecmp(cmd, "LIST") == 0) /* List akicks */
{
char *mask;
MYSQL_RES* res;
MYSQL_ROW row;
char *c;
mask = strtok(NULL, " ");
if(mask)
{
/* replace '*' with '%' for sql */
while((c = strchr(mask,'*'))) *c='%';
res = sql_query("SELECT mask, message, t_when+duration FROM cs_akick "
"WHERE scid=%d AND "
"mask LIKE %s", cr->scid, sql_str(mask));
}
else
res = sql_query("SELECT mask, message, t_when+duration FROM cs_akick WHERE scid=%d",
cr->scid);
if(res)
send_lang(u, s, AKICK_LIST_HEADER_X, mysql_num_rows(res));
while((row = sql_next_row(res)))
{
char buf[64];
struct tm *tm;
time_t t_expire = atoi(row[2]);
tm=localtime(&t_expire);
strftime(buf, sizeof(buf), format_str(u, DATE_FORMAT), tm);
send_lang(u, s, AKICK_LIST_X_X_X, row[0], row[1] ? row[1]: "", buf);
}
send_lang(u, s, AKICK_LIST_TAIL);
sql_free(res);
}
else
send_lang(u, s, CHAN_AKICK_SYNTAX);
CloseCR(cr);
}
/* this is called when a nick joins a registered channel */
int ev_cs_akick_chan_join(ChanRecord *cr, IRC_ChanNode* cn)
{
char* akick;
if(cr->extra[ED_AKICKS] == NULL)
load_akicks_for(cr);
akick = match_akick(cr, irc_UserMaskP(cn->user));
if(akick) /* we have an akick for the user joining */
{
char* reason = NULL;
IRC_Chan* chan;
if((chan = irc_FindChan(cr->name)) == NULL)
abort(); /* this should never happen */
if(sql_singlequery("SELECT message FROM cs_akick WHERE "
"scid=%d AND mask=%s", cr->scid, sql_str(akick)) < 1)
{
/* this akick is no longer on the db */
array_del_str(cr->extra[ED_AKICKS], akick);
return 0;
}
reason = sql_field(0);
if(chan->users_count == 1)
{
irc_ChanJoin(csu->u, cr->name, CU_MODE_ADMIN|CU_MODE_OP);
irc_AddCTimerEvent(chan, 30 , ev_cs_akick_timer_part, 0);
}
irc_ChanMode(chan->local_user ? chan->local_user : csu->u, chan,
"+b %s", akick);
irc_Kick(chan->local_user ? chan->local_user : csu->u, chan, cn->user,
reason ? reason : "AKICK");
mod_abort_event();
}
return 0;
}
/* Load akicks for a given channel */
void load_akicks_for(ChanRecord* cr)
{
MYSQL_RES* res;
MYSQL_ROW row;
int rowc = 0;
res = sql_query("SELECT mask FROM cs_akick WHERE scid=%d",
cr->scid);
if(res)
rowc = mysql_num_rows(res);
if(cr->extra[ED_AKICKS] != NULL)
array_free(cr->extra[ED_AKICKS]);
cr->extra[ED_AKICKS] = malloc(sizeof(darray));
array_init(cr->extra[ED_AKICKS], rowc, DA_STRING);
while((row = sql_next_row(res)))
array_add_str(cr->extra[ED_AKICKS], row[0]);
sql_free(res);
}
/* find for a given akick mask on a given channel */
int find_akick(ChanRecord* cr, char *mask)
{
if(cr->extra[ED_AKICKS] == NULL)
{
load_akicks_for(cr);
return (array_find_str(cr->extra[ED_AKICKS], mask) != -1);
}
else
{
if(array_find_str(cr->extra[ED_AKICKS], mask) != -1)
{
if(sql_singlequery("SELECT mask FROM cs_akick WHERE "
"scid=%d AND mask=%s", cr->scid, sql_str(mask)) < 1)
{
/* this akick is no longer on the db */
array_del_str(cr->extra[ED_AKICKS], mask);
return 0;
}
return 1;
}
}
return 0;
}
char* match_akick(ChanRecord *cr, char *usermask)
{
if(cr->extra[ED_AKICKS] == NULL)
load_akicks_for(cr);
return array_match_str(cr->extra[ED_AKICKS], usermask);
}
int mysql_insert_akick(u_int32_t scid, char* mask, char* message, int duration,
char *who)
{
return sql_execute("INSERT INTO cs_akick VALUES(%d, %s, %s, %s, %d, %d)",
scid, sql_str(mask), sql_str(message), sql_str(who),
irc_CurrentTime, duration);
}
int mysql_delete_akick(u_int32_t scid, char* mask)
{
if(strcasecmp(mask, "all") == 0)
return sql_execute("DELETE FROM cs_akick WHERE scid=%d",
scid);
else
return sql_execute("DELETE FROM cs_akick WHERE scid=%d AND mask=%s",
scid, sql_str(mask));
}
void ev_cs_akick_timer_part(IRC_Chan* chan, int tag)
{
/* check if we are still on chan */
if(chan->local_user == csu->u)
irc_ChanPart(csu->u, chan);
}
int ev_cs_akick_expire(void* dummy1, void* dummy2)
{
return sql_execute("DELETE FROM cs_akick WHERE t_when+duration<%d",
irc_CurrentTime);
}
/**
returns the number of roles that exist for a given channel
*/
int akick_count(u_int32_t scid)
{
int count;
count = sql_singlequery("SELECT count(*) FROM cs_akick WHERE scid=%d", scid);
if(count > 0)
return atoi(sql_field(0));
return 0;
}
/* this version takes care of sql upgrades */
int sql_upgrade(int version, int post)
{
MYSQL_RES* res;
MYSQL_ROW row;
switch(version)
{
case 2: /* pre-validation of integrity rules */
if(!post) /* we need to check for "lost" memos */
{
int rowc = 0;
res = sql_query("SELECT cs_akick.mask, cs_akick.scid FROM cs_akick"
" LEFT JOIN chanserv ON (cs_akick.scid = chanserv.scid)"
" WHERE cs_akick.scid IS NOT NULL AND chanserv.scid IS NULL");
while((row = sql_next_row(res)))
{
if(row[0] == 0) /* this will be set to null */
continue;
log_log(cs_log, mod_info.name, "Removing lost akick %s on %s",
row[0], row[1]);
sql_execute("DELETE FROM cs_akick WHERE scid=%s",
row[1]);
++rowc;
}
if(rowc)
log_log(cs_log, mod_info.name, "Removed %d lost akick(s)", rowc);
sql_free(res);
}
}
return 1;
}
/* End of Module */
syntax highlighted by Code2HTML, v. 0.9.1