/**********************************************************************
* PTlink IRC Services is (C) CopyRight PTlink IRC Software 1999-2006 *
* http://software.pt-link.net *
* This program is distributed under GNU Public License *
* Please read the file COPYING for copyright information. *
**********************************************************************
Description: chanserv module
*/
#include "chanserv.h"
#include "module.h"
#include "chanrecord.h"
#include "my_sql.h"
#include "dbconf.h"
#include "lang/common.lh"
#include "lang/chanserv.lh"
/* module, version, description */
SVS_Module mod_info =
{"chanserv", "5.1", "chanserv core module" };
#define DB_VERSION 4
/* ChangeLog
5.1 - #65: Fixed Chan Drop & BotServ
5.0 - #26, Added chanserv suspensions (DB_VERSION = 4)
4.1 - 0000321: HelpChan option to set +h when ops join the help chan
4.0 - 0000320: chanserv is not updating memory records during nick delete
0000317: ChanServ doesn't keep on the logchan when RestrictedChans is enabled
0000311: non registered nicks can bypass topiclock
0000305: foreign keys for data integrity
Added chanserv.3.sql changes
0000302: Add dconf option for DefaultMlock
3.0 - 0000265: remove nickserv cache system
0000261: mlock option
2.2 - 0000258: chanserv topiclock option
2.1 - sdata is now set/reset also for local users join/parts
2.0 - Added chanserv.2.sql changes
0000237: RestrictedChans option (to match the ircd option)
*/
/** functions/events we require **/
/* void (*FunctionPointer)(void);*/
int mysql_connection;
int e_nick_delete;
int e_expire;
int irc;
MOD_REQUIRES
MOD_FUNC(irc)
MOD_FUNC(dbconf_get_or_build)
MOD_FUNC(mysql_connection)
MOD_FUNC(e_nick_delete)
MOD_FUNC(e_expire)
MOD_END
/* functions we provide */
ServiceUser* chanserv_suser(void);
int e_regchan_join;
int e_chan_register;
int e_chan_delete;
MOD_PROVIDES
MOD_FUNC(chanserv_suser)
MOD_FUNC(e_regchan_join)
MOD_FUNC(e_chan_register)
MOD_FUNC(e_chan_delete)
MOD_END
/* core events */
void ev_cs_chan_join(IRC_Chan *chan , IRC_ChanNode *cnode);
void ev_cs_chan_part(IRC_Chan *chan , IRC_User *user);
void ev_cs_chan_topic(IRC_Chan *chan , IRC_User *user);
void ev_cs_deop(IRC_Chan *chan, IRC_User *user);
int ev_cs_nick_delete(int* snid, void *dummy);
int ev_cs_expire(void* dummy1, void* dummy2);
void ev_cs_new_server(IRC_Server* nserver, IRC_Server* from);
int sql_upgrade(int version, int post);
/* commands */
void cs_unknown(IRC_User* s, IRC_User* t);
/** Local config */
static char* Nick;
static char* Username;
static char* Hostname;
static char* Realname;
static char* LogChan;
static int ExpireTime;
static int MaxChansPerUser;
static int RestrictedChans;
static int NeedsAuth;
static char* DefaultMlock;
DBCONF_PROVIDES
DBCONF_WORD(Nick, "ChanServ", "Chanserv service nick")
DBCONF_WORD(Username, "Services", "Chanserv service username")
DBCONF_WORD(Hostname, "PTlink.net", "Chanserv service hostname")
DBCONF_STR(Realname, "Chanserv Service", "Chanserv service real name")
DBCONF_WORD_OPT(LogChan, "#Services.log", "Chanserv log channel")
DBCONF_TIME(ExpireTime,"15d",
"How long a channel will be kept without being used")
DBCONF_INT(MaxChansPerUser, "20", "How many channels an user can register")
DBCONF_SWITCH(RestrictedChans, "off",
"When services connect chanserv joins and leaves for all registered chans")
DBCONF_SWITCH(NeedsAuth, "off",
"Chanserv commands requires the nick to be authenticated")
DBCONF_STR_OPT(DefaultMlock, "+nt",
"Default mlock to be set on new registered channels")
DBCONF_END
/* Local variables */
ServiceUser csu;
int cs_log;
/* this is called before load and at services rehash */
int mod_rehash(void)
{
if(dbconf_get_or_build(mod_info.name, dbconf_provides) < 0 )
{
errlog("Error reading dbconf!");
return -1;
}
return 0;
}
int mod_load(void)
{
if(ExpireTime> 0 && ExpireTime < 86400)
{
errlog("ExpireTime is too low, minimum is 1 day!");
return -6;
}
/* first try to open the log file */
cs_log = log_open("chanserv","chanserv");
if(cs_log<0)
{
errlog("Could not open chanserv log file!");
return -1;
}
if(sql_check_inst_upgrade(mod_info.name, DB_VERSION, sql_upgrade) < 0)
return -4;
/* Create chanserv user */
csu.u = irc_CreateLocalUser(Nick, Username, Hostname, Hostname,
Realname,"+ro");
if(LogChan)
{
IRC_Chan *chan;
log_set_irc(cs_log, Nick, LogChan);
chan = irc_ChanJoin(csu.u, LogChan, CU_MODE_ADMIN|CU_MODE_OP);
irc_ChanMode(csu.u, chan, "+Ostn");
}
irc_AddUMsgEvent(csu.u, "*", cs_unknown); /* any other msg handler */
/* Add user events */
irc_AddEvent(ET_CHAN_JOIN, ev_cs_chan_join);
irc_AddEvent(ET_CHAN_PART, ev_cs_chan_part);
irc_AddEvent(ET_CHAN_TOPIC, ev_cs_chan_topic);
/* irc_AddCmodeChange("-o", ev_cs_deop); */
/* reset chanserv status */
sql_query("UPDATE chanserv SET status=0 WHERE status<>0");
/* setup delete routines */
mod_add_event_action(e_nick_delete, (ActionHandler) ev_cs_nick_delete);
mod_add_event_action(e_expire, (ActionHandler) ev_cs_expire);
if(RestrictedChans)
irc_AddEvent(ET_NEW_SERVER, ev_cs_new_server);
return 0;
}
void mod_unload(void)
{
/* remove chanserv and all associated events */
irc_QuitLocalUser(csu.u, "Removing service");
/* delete events */
irc_DelEvent(ET_CHAN_JOIN, ev_cs_chan_join);
irc_DelEvent(ET_CHAN_PART, ev_cs_chan_part);
if(RestrictedChans)
irc_DelEvent(ET_NEW_SERVER, ev_cs_new_server);
}
void cs_unknown(IRC_User* s, IRC_User* t)
{
send_lang(t, s, UNKNOWN_COMMAND, irc_GetLastMsgCmd());
}
/* this is called after a join */
void ev_cs_chan_join(IRC_Chan* chan, IRC_ChanNode* cn)
{
ChanRecord *cr;
int remote_users;
remote_users = chan->users_count - chan->lusers_count;
cr = chan->sdata;
if(cr == NULL)
{
cr = OpenCR(chan->name);
chan->sdata = cr;
}
/* we don't do nothing for local users */
if(irc_IsLocalUser(cn->user))
return;
/* first remote user joining the channel */
if(remote_users == 1)
{
/* first remote user joining a registered channel */
if(cr)
{
irc_ChanMode(csu.u, chan, "+r");
if(cr->mlock) /* we just set, the apply is called on next mode */
irc_ChanMLockSet(csu.u, chan, cr->mlock);
if(irc_ConnectionStatus() == 2)
send_lang(cn->user, csu.u, CHAN_X_IS_REGISTERED, chan->name);
if(cr->last_topic_setter && cr->last_topic)
{
if((chan->last_topic == NULL) ||
(chan->last_topic && (strcmp(chan->last_topic, cr->last_topic)!=0)))
irc_ChanTopic(chan, cr->last_topic_setter, cr->t_ltopic, cr->last_topic);
}
}
else if(irc_ConnectionStatus() == 2)
send_lang(cn->user, csu.u, CHAN_1ST_X_X_X,
cn->user->nick, chan->name, chan->name);
}
/* for all registered channels */
if(cr)
{
if(irc_ConnectionStatus() == 2)
{
if(cr->url)
irc_SendRaw(NULL, "328 %s %s : %s",
cn->user->nick, cr->name, cr->url);
if(cr->entrymsg)
irc_SendNotice(cn->user, csu.u, "%s %s",
cr->name, cr->entrymsg);
}
/* update maxusers if required */
if(remote_users > cr->maxusers) /* new record*/
{
cr->maxusers = remote_users;
cr->t_maxusers = irc_CurrentTime;
UpdateCR(cr);
}
/* call the event */
mod_do_event(e_regchan_join, cr, cn);
}
}
/* this is called when a user parts */
void ev_cs_chan_part(IRC_Chan* chan, IRC_User *user)
{
if(chan->users_count == 1 && chan->sdata)
{
CloseCR(chan->sdata);
chan->sdata = NULL;
}
}
void ev_cs_deop(IRC_Chan *chan, IRC_User *user)
{
/* send_lang(user, csu.u, CHAN_DEOP, user->nick, chan->name); */
}
/* to return the nickserv client */
ServiceUser* chanserv_suser(void)
{
return &csu;
}
/* this is called when a nick is deleted */
int ev_cs_nick_delete(int *snid, void *dummy)
{
MYSQL_RES* res;
MYSQL_ROW row;
ChanRecord* cr;
/* remove the nick from successors */
res = sql_query("SELECT scid, name FROM chanserv WHERE successor=%d", *snid);
while((row = sql_next_row(res)))
{
u_int32_t scid = atoi(row[0]);
IRC_Chan *chan;
log_log(cs_log, mod_info.name, "Removing successor on %s (was %d)",
row[1], *snid);
chan = irc_FindChan(row[1]);
if(chan && (cr == chan->sdata))
cr->successor = 0;
sql_execute("UPDATE chanserv SET successor=NULL"
" WHERE scid=%d", scid);
}
sql_free(res);
/* transfer owned channel to successors */
res = sql_query("SELECT scid, name, successor FROM chanserv WHERE founder=%d"
" AND successor IS NOT NULL", *snid);
while((row = sql_next_row(res)))
{
u_int32_t scid = atoi(row[0]);
IRC_Chan *chan;
log_log(cs_log, mod_info.name, "Transfering channel %s (from %d to %s)",
row[1], *snid, row[2]);
chan = irc_FindChan(row[1]);
if(chan && (cr == chan->sdata))
{
cr->founder = cr->successor;
cr->successor = 0;
}
sql_execute("UPDATE chanserv SET founder=successor, successor=NULL"
" WHERE scid=%d", scid);
}
sql_free(res);
/*
sql_execute("UPDATE chanserv SET founder=successor, successor=NULL WHERE founder=%d"
" AND successor IS NOT NULL", *snid);
*/
/* now delete remaining owned channels */
res = sql_query("SELECT scid, name FROM chanserv WHERE founder=%d",
*snid);
while((row = sql_next_row(res)))
{
u_int32_t scid = atoi(row[0]);
IRC_Chan *chan;
sql_execute("DELETE FROM chanserv WHERE scid=%d", scid);
chan = irc_FindChan(row[1]);
if(chan && chan->sdata)
{
irc_ChanMode(csu.u, chan, "-r");
CloseCR(chan->sdata);
chan->sdata = NULL;
if(chan->local_user)
irc_ChanPart(chan->local_user, chan);
}
log_log(cs_log, mod_info.name, "Deleted channel %d, %s from deleted nick %d",
scid, row[1], *snid);
mod_do_event(e_chan_delete, &scid, NULL);
}
sql_free(res);
return 0;
}
void ev_cs_chan_topic(IRC_Chan *chan , IRC_User *user)
{
ChanRecord* cr = chan->sdata;
u_int32_t snid = 0;
if(user)
snid = user->snid;
if(cr)
{
if(IsTopicLock(cr) && ((snid==0) || (snid != cr->founder)))
{
if(cr->last_topic_setter && cr->last_topic)
{
if((chan->last_topic == NULL) ||
(chan->last_topic && (strcmp(chan->last_topic, cr->last_topic)!=0)))
irc_ChanTopic(chan, cr->last_topic_setter, cr->t_ltopic, cr->last_topic);
}
}
else
{
FREE(cr->last_topic);
SDUP(cr->last_topic, chan->last_topic);
FREE(cr->last_topic_setter);
SDUP(cr->last_topic_setter, chan->last_topic_setter);
UpdateCR(cr);
}
}
}
/*
* Expires chans
*/
int ev_cs_expire(void* dummy1, void* dummy2)
{
time_t expire_start;
MYSQL_RES* res;
MYSQL_ROW row;
int rowc = 0;
/* expire code goes here */
res = sql_query ("SELECT scid, name FROM chanserv WHERE "
"(flags & %d = 0) AND t_last_use < %d",
CFL_NOEXPIRE, time(NULL)-ExpireTime);
if(res)
rowc = mysql_num_rows(res);
if(rowc == 0)
{
sql_free(res);
return 0;
}
log_log(cs_log, mod_info.name, "Will expire %d chan(s)", rowc);
expire_start = time(NULL);
rowc = 0;
/* fetch the data */
while((row = sql_next_row(res)))
{
IRC_Chan* chan;
u_int32_t scid = atoi(row[0]);
rowc++;
chan = irc_FindChan(row[1]);
if(chan && chan->sdata)
{
irc_ChanMode(csu.u, chan, "-r");
CloseCR(chan->sdata);
chan->sdata = NULL;
if(chan->local_user)
irc_ChanPart(chan->local_user, chan);
}
log_log(cs_log, mod_info.name, "Expiring scid %d, %s", scid, row[1]);
/* first call related actions */
mod_do_event(e_chan_delete, &scid, NULL);
/* now really delete it */
sql_execute("DELETE FROM chanserv WHERE scid=%d", scid);
}
sql_free(res);
log_log(cs_log, mod_info.name, "Expired %d channel(s), took %s",
rowc, str_time(time(NULL) - expire_start));
/* expire suspensions */
res = sql_query("SELECT scid FROM chanserv_suspensions "
"WHERE duration>0 AND t_when+duration<%d", irc_CurrentTime);
while((row = sql_next_row(res)))
{
u_int32_t scid = atoi(row[0]);
log_log(cs_log, mod_info.name, "Expiring chan suspension for %d",
scid);
sql_execute("DELETE FROM chanserv_suspensions WHERE scid=%d", scid);
sql_execute("UPDATE chanserv SET flags = (flags & ~%d)"
" WHERE scid=%d", CFL_SUSPENDED, scid);
/*
sql_execute("UPDATE chanserv SET flags = (flags & ~%d), t_expire=%d"
" WHERE snid=%d", CFL_SUSPENDED, irc_CurrentTime+ExpireTime, snid);
*/
}
sql_free(res);
return 0;
}
void ev_cs_new_server(IRC_Server* nserver, IRC_Server *from)
{
static int already_loaded = 0;
MYSQL_RES* res;
MYSQL_ROW row;
if(already_loaded)
return;
res = sql_query("SELECT name FROM chanserv");
while((row = sql_next_row(res)))
{
IRC_Chan *chan = irc_FindChan(row[0]);
if(chan)
continue;
chan = irc_ChanJoin(csu.u, row[0], 0);
irc_ChanMode(csu.u, chan, "+r");
irc_ChanPart (csu.u, chan);
}
sql_free(res);
already_loaded = -1;
}
/* this version takes care of sql upgrades */
int sql_upgrade(int version, int post)
{
MYSQL_RES* res;
MYSQL_ROW row;
switch(version)
{
case 3: /* pre-validation of integrity rules */
if(!post) /* we need to check for "lost" memos */
{
int rowc = 0;
res = sql_query("SELECT"
" chanserv.successor, chanserv.name, chanserv.scid FROM chanserv"
" LEFT JOIN nickserv ON (chanserv.successor = nickserv.snid)"
" WHERE chanserv.successor IS NOT NULL AND nickserv.snid IS NULL");
while((row = sql_next_row(res)))
{
if(atoi(row[0]) == 0) /* this will be set to null */
continue;
log_log(cs_log, mod_info.name, "Removing lost successor %s on %s",
row[0], row[1]);
sql_execute("UPDATE chanserv SET successor=NULL WHERE scid=%s",
row[2]);
++rowc;
}
if(rowc)
log_log(cs_log, mod_info.name, "Removed %d lost successor(s)", rowc);
sql_free(res);
rowc = 0;
res = sql_query("SELECT"
" chanserv.founder, chanserv.name, chanserv.successor,"
" chanserv.scid FROM chanserv"
" LEFT JOIN nickserv ON (chanserv.founder = nickserv.snid)"
" WHERE chanserv.successor IS NOT NULL AND nickserv.snid IS NULL");
while((row = sql_next_row(res)))
{
if(atoi(row[0]) == 0) /* this will be set to null */
continue;
if(row[2] && atoi(row[2]) != 0)
{
log_log(cs_log, mod_info.name,
"Transfering lost channel %s to %s", row[1], row[2]);
sql_execute("UPDATE chanserv SET founder=%s, successor=NULL"
" WHERE scid=%s", row[2], row[3]);
}
else
{
log_log(cs_log, mod_info.name,
"Deleting lost channel %s, founder was %s", row[3], row[0]);
sql_execute("DELETE FROM chanserv WHERE scid=%s",
row[3]);
++rowc;
}
}
if(rowc)
log_log(cs_log, mod_info.name, "Deleted %d lost channels(s)", rowc);
sql_free(res);
}
}
return 1;
}
syntax highlighted by Code2HTML, v. 0.9.1