/* trircd protocol module for IRC Services.
* Provided by Yusuf Iskenderoglu <uhc0@stud.uni-karlsruhe.de>
*
* 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 "language.h"
#include "messages.h"
#include "modules/chanserv/chanserv.h"
#include "modules/operserv/operserv.h"
#include "modules/nickserv/nickserv.h"
#include "banexcept.h"
#include "chanprot.h"
#include "halfop.h"
#include "invitemask.h"
#include "sjoin.h"
#include "svsnick.h"
#include "token.h"
/*************************************************************************/
static Module *module;
static Module *module_operserv;
static Module *module_chanserv;
static char *NetworkDomain = NULL;
/*
* We import `s_ChanServ' from the "chanserv/main" module; this variable
* holds a pointer to `s_ChanServ', obtained via get_module_symbol(). When
* the ChanServ module is not loaded, this holds a pointer to `ServerName'
* instead as a default value.
*
* Note that we use a #define here to substitute `*p_s_ChanServ' for
* `s_ChanServ', so subsequent code can simply use `s_ChanServ' as if it
* were declared normally.
*/
static char **p_s_ChanServ = &ServerName;
#define s_ChanServ (*p_s_ChanServ)
/*************************************************************************/
/***************** Local interface to external routines ******************/
/*************************************************************************/
static typeof(is_services_admin) *p_is_services_admin = NULL;
static int local_is_services_admin(User *u) {
return p_is_services_admin && (*p_is_services_admin)(u);
}
#define is_services_admin local_is_services_admin
static typeof(get_channelinfo) *p_get_channelinfo = NULL;
static ChannelInfo *local_get_channelinfo(const char *name) {
return p_get_channelinfo ? (*p_get_channelinfo)(name) : NULL;
}
#define get_channelinfo local_get_channelinfo
static typeof(first_channelinfo) *p_first_channelinfo = NULL;
static ChannelInfo *local_first_channelinfo(void) {
return p_first_channelinfo ? (*p_first_channelinfo)() : NULL;
}
#define first_channelinfo local_first_channelinfo
static typeof(next_channelinfo) *p_next_channelinfo = NULL;
static ChannelInfo *local_next_channelinfo(void) {
return p_next_channelinfo ? (*p_next_channelinfo)() : NULL;
}
#define next_channelinfo local_next_channelinfo
/*************************************************************************/
/************************** User/channel modes ***************************/
/*************************************************************************/
struct modedata_init {
uint8 mode;
ModeData data;
};
static const struct modedata_init new_usermodes[] = {
{'g', {0x00000008}}, /* Receive globops */
{'h', {0x00000010}}, /* Helpop */
{'r', {0x00000020,0,0,0,MI_REGISTERED}},
/* Registered nick */
{'a', {0x00000040}}, /* Services admin */
{'A', {0x00000080}}, /* Server admin */
{'x', {0x00000100}}, /* Mask hostname */
{'R', {0x00000200}}, /* Do not receive text from unregged nicks */
{'L', {0x00000400}}, /* Nick language has been set */
{'c', {0x00000800}}, /* No color in private messages */
{'C', {0x00001000}}, /* No CTCPs in private messages */
{'H', {0x00002000}}, /* User can see realhost, secret channels */
{'p', {0x00004000}}, /* Hide idle time in /whois */
{'P', {0x00008000}}, /* Do not receive private messages */
{'t', {0x00010000}}, /* Greek->Greeklish translation active */
{'z', {0x00020000}}, /* Dccallow all users */
};
static const struct modedata_init new_chanmodes[] = {
{'R', {0x00000100,0,0,0,MI_REGNICKS_ONLY}},
/* Only identified users can join */
{'r', {0x00000200,0,0,0,MI_REGISTERED}},
/* Set for all registered channels */
{'c', {0x00000400,0,0}}, /* No ANSI colors in channel */
{'O', {0x00000800,0,0,0,MI_OPERS_ONLY}},
/* Only opers can join channel */
{'T', {0x00001000,0,0}}, /* /topic only by protected (+a) */
{'x', {0x00002000,0,0}}, /* Hide ops (show no .@%+ prefixes) */
{'N', {0x00004000,0,0}}, /* No clients with unresolved hostnames */
{'d', {0x00008000,0,0}}, /* Hide part/quit reasons */
{'f', {0x00010000,1,0}}, /* Flood limit */
{'C', {0x00020000,0,0}}, /* No CTCP in channel */
{'g', {0x00040000,0,0}}, /* Only registered nicks can talk */
{'j', {0x00080000,0,0}}, /* /names only by members */
{'L', {0x00100000,1,0}}, /* Channel link */
{'K', {0x00200000,0,0}}, /* No knock */
{'J', {0x00400000,1,0}}, /* /join delay */
{'I', {0x80000000,1,1,0,MI_MULTIPLE}}, /* INVITE hosts */
{'e', {0x80000000,1,1,0,MI_MULTIPLE}}, /* ban exceptions */
{'M', {0x80000000,1,1,0,MI_MULTIPLE}}, /* moderated hosts */
{'z', {0x80000000,1,1,0,MI_MULTIPLE}}, /* zapped channels */
};
static const struct modedata_init new_chanusermodes[] = {
{'h', {0x00000004,1,1,'%'}}, /* Half-op */
{'a', {0x00000008,1,1,'~'}}, /* Protected (no kick or deop by +o) */
{'u', {0x00000010,1,1,'.',MI_CHANOWNER}},
/* Channel owner */
};
static void init_modes(void)
{
int i;
for (i = 0; i < lenof(new_usermodes); i++)
usermodes[new_usermodes[i].mode] = new_usermodes[i].data;
for (i = 0; i < lenof(new_chanmodes); i++)
chanmodes[new_chanmodes[i].mode] = new_chanmodes[i].data;
for (i = 0; i < lenof(new_chanusermodes); i++)
chanusermodes[new_chanusermodes[i].mode] = new_chanusermodes[i].data;
mode_setup();
};
/*************************************************************************/
/* Language hash values (for usermode +L) */
#define HASH_MODULUS 387
static int langhash[NUM_LANGS];
static struct {
int lang;
const char *str;
} langhash_init[] = {
{ LANG_EN_US, "English" },
{ LANG_JA_EUC, "Japanese-EUC" },
{ LANG_JA_SJIS, "Japanese-SJIS" },
{ LANG_ES, "Espa\361ol" },
{ LANG_PT, "Portugues" },
{ LANG_FR, "French" },
{ LANG_TR, "Turkce" },
{ LANG_NL, "Nederlands" },
{ LANG_DE, "Deutsch" },
{ LANG_IT, "Italian" },
{ LANG_HU, "Magyar" },
};
static void init_langhash()
{
int i;
memset(langhash, 0, sizeof(langhash));
for (i = 0; i < lenof(langhash_init); i++) {
int hashval = 0;
const unsigned char *s = (const unsigned char *)langhash_init[i].str;
while (*s)
hashval += *s++ & 0xDF;
langhash[langhash_init[i].lang] = hashval % HASH_MODULUS;
}
}
/*************************************************************************/
/************************* IRC message receiving *************************/
/*************************************************************************/
/* Please note that the new CLIENT string of TRIRCD-5 cannot be supported
* unless ircservices implements identity management of Bahamut 1.6 series.
* Therefore, the good old NICK line with the IP will be used. -TimeMr14C */
static void m_nick(char *source, int ac, char **av)
{
char *newmodes, *fakehost, ipbuf[16], *s;
uint32 ip;
if (*source) {
/* Old user changing nicks. */
if (ac != 2) {
if (debug)
module_log("debug: NICK message: wrong number of parameters"
" (%d) for source `%s'", ac, source);
} else {
do_nick(source, ac, av);
}
return;
}
/* New user. */
if (ac != 11) {
if (debug)
module_log("debug: NICK message: wrong number of parameters (%d)"
" for new user", ac);
return;
}
/* With Bahamut, new users get modes in the NICK message. Save the
* modes and strip the parameter out. */
newmodes = av[3];
memmove(&av[3], &av[4], sizeof(char *) * 7);
ac--;
fakehost = av[5];
memmove(&av[5], &av[6], sizeof(char *) * 4);
ac--;
/* Convert IP address from 32 bit integer to string format */
ip = strtoul(av[7], &s, 10);
if (*s) {
wallops(NULL,
"\2WARNING\2: invalid IP address `%s' for new nick %s",
av[7], av[0]);
module_log("WARNING: invalid IP address `%s' for new nick %s",
av[7], av[0]);
s = NULL;
} else if (!ip && find_module("operserv/sline")) {
static int warned_no_nickip = 0;
if (!warned_no_nickip) {
wallops(NULL,
"\2WARNING\2: missing IP address for new nick %s."
" Make sure you have no pre-4.0 servers on your"
" network, or SZLINEs will not work correctly.",
av[0]);
warned_no_nickip = 1;
}
module_log("WARNING: missing IP address for new nick %s", av[0]);
s = strcpy(ipbuf, "0.0.0.0");
} else {
uint8 rawip[4];
rawip[0] = ip>>24;
rawip[1] = ip>>16;
rawip[2] = ip>>8;
rawip[3] = ip;
s = unpack_ip(rawip);
if (!s || strlen(s) > sizeof(ipbuf)-1) {
/* super duper paranoia */
module_log("WARNING: unpack_ip() returned overlong or null"
" string: %s", s ? s : "(null)");
s = NULL;
} else {
strcpy(ipbuf, s); /* safe: we checked length above */
s = ipbuf;
}
}
/* Rearrange parameters for do_nick() (IP address is in `s') */
av[7] = av[6];
av[6] = av[8];
av[8] = s;
/* Move fakehost into user argument area */
av[9] = fakehost;
if (do_nick(source, ac, av)) {
av[1] = newmodes;
do_umode(av[0], 2, av);
}
}
/*************************************************************************/
static void m_sjoin(char *source, int ac, char **av)
{
if (ac == 3 || ac < 2) {
if (debug)
module_log("debug: SJOIN: expected 2 or >=4 params, got %d", ac);
return;
}
do_sjoin(source, ac, av);
}
/*************************************************************************/
static void m_capab(char *source, int ac, char **av)
{
int got_trircd5 = 0, got_excap = 0;
int i;
for (i = 0; i < ac; i++) {
if (stricmp(av[i], "TRIRCD5") == 0) {
got_trircd5 = 1;
} else if (stricmp(av[i], "EXCAP") == 0) {
got_excap = 1;
}
}
if (!got_trircd5 || !got_excap) {
send_error("Only trircd 5.5 and later are supported");
strscpy(quitmsg, "Remote server version is not 5.5 or later",
sizeof(quitmsg));
quitting = 1;
}
}
static void m_excap(char *source, int ac, char **av)
{
char *s;
if (ac < 1)
return;
for (s = strtok(av[0]," "); s != NULL; s = strtok(NULL," ")) {
if (stricmp(s, "CHANLINK") == 0) {
}
}
}
/*************************************************************************/
static void m_svinfo(char *source, int ac, char **av)
{
/* -TimeMr14C
* Obviously we do need to send an initial PING, regardless of the
* reality that services may be configured to send pings if there is
* low traffic. Additionally, to comply with the TRIRCD Extensions, we
* do set the topic of the services logging channel.
*/
send_cmd(NULL, "PING :%s", ServerName);
send_cmd(NULL, "TOPIC #services DevNull %ld :Services Logging channel", (long)time(NULL));
}
/*************************************************************************/
static void m_tmode(char *source, int ac, char **av)
{
if (ac < 3) {
if (debug)
module_log("debug: TMODE: expected >=3 params, got %d", ac);
return;
}
memmove(av+1, av+2, sizeof(char *) * (ac-1));
do_cmode(source, ac-1, av);
}
/*************************************************************************/
static Message trircd_messages[] = {
{ "ADMINS", NULL },
{ "AKILL", NULL },
{ "CAPAB", m_capab },
{ "CLIENT", NULL },
{ "DENYTEXT", NULL },
{ "EWHOIS", NULL },
{ "EXCAP", m_excap },
{ "EXCLUDE", NULL },
{ "GLINE", NULL },
{ "GLOBOPS", NULL },
{ "GNOTICE", NULL },
{ "GOPER", NULL },
{ "HASH", NULL },
{ "JUPITER", NULL },
{ "KNOCK", NULL },
{ "MYID", NULL },
{ "NETHTM", NULL },
{ "NETSET", NULL },
{ "NICK", m_nick },
{ "RAKILL", NULL },
{ "REXCLUDE", NULL },
{ "REXCOM", NULL },
{ "RNOTICE", NULL },
{ "RPING", NULL },
{ "RPUNG", NULL },
{ "SADMINS", NULL },
{ "SGLINE", NULL },
{ "SILENCE", NULL },
{ "SJOIN", m_sjoin },
{ "SQLINE", NULL },
{ "SVINFO", m_svinfo },
{ "SZLINE", NULL },
{ "TMODE", m_tmode },
{ "UNDENYTEXT",NULL },
{ "UNGLINE", NULL },
{ "UNJUPITER", NULL },
{ "UNSGLINE", NULL },
{ "UNSQLINE", NULL },
{ "UNSZLINE", NULL },
{ NULL }
};
static TokenInfo trircd5_tokens[] = {
/* 0x21 */
{ "!", NULL },
{ "\"", NULL },
{ "#", NULL },
{ "$", NULL },
{ "%", NULL },
{ "&", NULL },
{ "'", NULL },
{ "(", NULL },
{ ")", NULL },
{ "*", NULL },
{ "+", NULL },
{ ",", "EWHOIS" },
{ "-", NULL },
{ ".", NULL },
{ "/", NULL },
/* 0x30 */
{ "0", NULL },
{ "1", "SVSKILL" },
{ "2", "SVSMODE" },
{ "3", "SVSNICK" },
{ "4", "SVSNOOP" },
{ "5", "SGLINE" },
{ "6", "UNSGLINE" },
{ "7", "UNSZLINE" },
{ "8", NULL },
{ "9", NULL },
{ ":", NULL },
{ ";", NULL },
{ "<", "ADMINS" },
{ "=", "MYID" },
{ ">", "SADMINS" },
{ "?", "STATS" },
/* 0x40 */
{ "@", "LUSERS" },
{ "A", "AWAY" },
{ "B", "BURST" },
{ "C", "JUPITER" },
{ "D", "UNJUPE" },
{ "E", "ERROR" },
{ "F", "GLINE" },
{ "G", "GNOTICE" },
{ "H", "CLIENT" },
{ "I", "INVITE" },
{ "J", "JOIN" },
{ "K", "KICK" },
{ "L", "GLOBOPS" },
{ "M", "MODE" },
{ "N", "NICK" },
{ "O", "SQUERY" },
/* 0x50 */
{ "P", "PRIVMSG" },
{ "Q", "QUIT" },
{ "R", "RAKILL" },
{ "S", "SJOIN" },
{ "T", "TOPIC" },
{ "U", "UNSQLINE" },
{ "V", "NETSET" },
{ "W", "WHOIS" },
{ "X", "SERVICE" },
{ "Y", "INFO" },
{ "Z", "ZLINE" },
{ "[", "RPING" },
{ "\\", "USERS" },
{ "]", "RPONG" },
{ "^", NULL },
{ "_", "DENYTEXT" },
/* 0x60 */
{ "`", "UNDENYTEXT" },
{ "a", "AKILL" },
{ "b", "SVSJOIN" },
{ "c", "CONNECT" },
{ "d", "ADMIN" },
{ "e", "EXCLUDE" },
{ "f", "PING" },
{ "g", "GOPER" },
{ "h", "SILENCE" },
{ "i", "TIME" },
{ "j", "REXCLUDE" },
{ "k", "KILL" },
{ "l", "WALLOPS" },
{ "m", "MOTD" },
{ "n", "NOTICE" },
{ "o", NULL },
/* 0x70 */
{ "p", "PART" },
{ "q", "SQUIT" },
{ "r", "RNOTICE" },
{ "s", "SERVER" },
{ "t", "TRACE" },
{ "u", "PONG" },
{ "v", "VERSION" },
{ "w", "WHOWAS" },
{ "x", "SQLINE" },
{ "y", "UNGLINE" },
{ "z", "SVINFO" },
{ "{", "KNOCK" },
{ "|", "REXCOM" },
{ "}", NULL },
{ "~", "TMODE" },
{ NULL }
};
/*************************************************************************/
/************************** IRC message sending **************************/
/*************************************************************************/
/* Send a NICK command for a new user. */
static void do_send_nick(const char *nick, const char *user, const char *host,
const char *server, const char *name,
const char *modes)
{
/* NICK <nick> <hops> <TS> <umode> <user> <host> <server> <svsid>
* :<ircname> */
send_cmd(NULL, "NICK %s 1 %ld +%s %s %s %s %s 0 0 :%s", nick,
(long)time(NULL), modes, user, host, host, server, name);
}
/*************************************************************************/
/* Send a NICK command to change an existing user's nick. */
static void do_send_nickchange(const char *nick, const char *newnick)
{
send_cmd(nick, "NICK %s %ld", newnick, (long)time(NULL));
}
/*************************************************************************/
/* Send a command to change a user's "real name". */
static void do_send_namechange(const char *nick, const char *newname)
{
/* Not supported by this protocol. */
}
/*************************************************************************/
/* Send a SERVER command, and anything else needed at the beginning of the
* connection.
*/
static void do_send_server(void)
{
send_cmd(NULL, "PASS %s :TS7", RemotePassword);
/* for 5.7 only: send_cmd(NULL, "CAPAB NICKIP TOKEN1 EXCAP"); */
send_cmd(NULL, "CAPAB TS3 NOQUIT SSJOIN NICKIP DT1 EX-REX TOKEN1 TMODE EXCAP");
send_cmd(NULL, "EXCAP :CHANLINK");
send_cmd(NULL, "SERVER %s 1 :%s", ServerName, ServerDesc);
send_cmd(NULL, "SVINFO 3 3 0 :%ld", (long)time(NULL));
}
/*************************************************************************/
/* Send a SERVER command for a remote (juped) server. */
/* Even with TRIRCD5, services does not need supporting the new SERVER
* line, where identity and capability parameters are also sent.
* 1) Services does not need to know, who is also ULined.
* 2) Services does not support servername hiding.
* 3) Services does not do network rerouting.
* So are both parameters unnecessary and old SERVER line will be used.
* -TimeMr14C */
static void do_send_server_remote(const char *server, const char *reason)
{
send_cmd(NULL, "SERVER %s 2 :%s", server, reason);
}
/*************************************************************************/
/* Send a WALLOPS (really a GLOBOPS). */
static void do_wallops(const char *source, const char *fmt, ...)
{
va_list args;
char buf[BUFSIZE];
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
send_cmd(source ? source : ServerName, "GLOBOPS :%s", buf);
}
/*************************************************************************/
/* Send a NOTICE to all users on the network. */
static void do_notice_all(const char *source, const char *fmt, ...)
{
va_list args;
char msgbuf[BUFSIZE];
va_start(args, fmt);
vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
va_end(args);
if (NetworkDomain) {
send_cmd(source, "NOTICE $*.%s :%s", NetworkDomain, msgbuf);
} else {
/* Go through all common top-level domains. If you have others,
* add them here. */
send_cmd(source, "NOTICE $*.com :%s", msgbuf);
send_cmd(source, "NOTICE $*.net :%s", msgbuf);
send_cmd(source, "NOTICE $*.org :%s", msgbuf);
send_cmd(source, "NOTICE $*.edu :%s", msgbuf);
}
}
/*************************************************************************/
/* Send a command which modifies channel status. */
static void do_send_channel_cmd(const char *source, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vsend_cmd(source, fmt, args);
va_end(args);
}
/*************************************************************************/
/******************************* Callbacks *******************************/
/*************************************************************************/
static int do_connect(void)
{
/* Send MODEs to the server for all channels locked +L, in case the
* servers aren't aware of them. */
ChannelInfo *ci;
for (ci = first_channelinfo(); ci; ci = next_channelinfo()) {
if ((ci->mlock_on & mode_char_to_flag('L',MODE_CHANNEL))
&& ci->mlock_link
) {
send_cmd(s_ChanServ, "MODE %s +L %s", ci->name, ci->mlock_link);
}
}
return 0;
}
/*************************************************************************/
static int do_receive_message(const char *source, const char *cmd, int ac,
char **av)
{
/* Watch for MODE +/-L's for nonexistent channels, and:
* (1) reverse them if necessary
* (2) don't generate "no such channel" error messages for them
*/
char *s;
ChannelInfo *ci;
int add;
int modeL; /* status of +/-L wrt this message (1:on, 0:off, -1:not seen) */
int lockL; /* status of MLOCK +/-L (1:+L, 0:-L) */
if (stricmp(cmd,"MODE") != 0 || ac < 2 || av[0][0] != '#'
|| get_channel(av[0]) /* existing channels will get handled normally */
|| !(ci = get_channelinfo(av[0]))
) {
return 0;
}
if ((ci->mlock_on & mode_char_to_flag('L',MODE_CHANNEL)) && ci->mlock_link)
lockL = 1;
else if (ci->mlock_off & mode_char_to_flag('L',MODE_CHANNEL))
lockL = 0;
else
return 0;
add = -1;
modeL = -1;
for (s = av[1]; *s; s++) {
if (*s == '+') {
add = 1;
} else if (*s == '-') {
add = 0;
} else if (*s == 'L') {
if (add < 0) {
module_log("Invalid MODE message from server: MODE %s",
merge_args(ac,av));
return 0;
} else {
modeL = add;
}
}
}
if (modeL == -1)
return 0;
if (modeL != lockL) {
if (lockL) {
/* ci->mlock_link checked above */
send_cmd(s_ChanServ, "MODE %s +L %s", av[0], ci->mlock_link);
} else {
send_cmd(s_ChanServ, "MODE %s -L", av[0]);
}
}
return 1; /* don't let the main code see it and say "no such channel" */
}
/*************************************************************************/
static int do_user_create(User *user, int ac, char **av)
{
user->fakehost = sstrdup(av[9]);
return 0;
}
/*************************************************************************/
static int do_user_servicestamp_change(User *user)
{
send_cmd(ServerName, "SVSMODE %s +d %ld",
user->nick, (long)user->servicestamp);
return 0;
}
/*************************************************************************/
static int do_user_mode(User *user, int modechar, int add, char **av)
{
switch (modechar) {
case 'd':
module_log("MODE tried to change services stamp for %s", user->nick);
send_cmd(ServerName, "SVSMODE %s +d %u", user->nick,
user->servicestamp);
return 0;
case 'o':
if (add) {
user->mode |= UMODE_o;
if (add && user_identified(user) && is_services_admin(user))
send_cmd(ServerName, "SVSMODE %s +a", user->nick);
user->mode &= ~UMODE_o;
}
return 0;
case 'r':
if (user_identified(user)) {
if (!add)
send_cmd(ServerName, "SVSMODE %s +r", user->nick);
} else {
if (add)
send_cmd(ServerName, "SVSMODE %s -r", user->nick);
}
return 1;
case 'a':
if (is_oper(user)) {
if (is_services_admin(user)) {
if (!add)
send_cmd(ServerName, "SVSMODE %s +a", user->nick);
} else {
if (add)
send_cmd(ServerName, "SVSMODE %s -a", user->nick);
}
return 1;
}
}
return 0;
}
/*************************************************************************/
static int do_channel_mode(const char *source, Channel *channel,
int modechar, int add, char **av)
{
int32 flag = mode_char_to_flag(modechar, MODE_CHANNEL);
switch (modechar) {
case 'L':
free(channel->link);
if (add) {
channel->mode |= flag;
channel->link = sstrdup(av[0]);
} else {
channel->mode &= ~flag;
channel->link = NULL;
}
return 1;
case 'f':
free(channel->flood);
if (add) {
channel->mode |= flag;
channel->flood = sstrdup(av[0]);
} else {
channel->mode &= ~flag;
channel->flood = NULL;
}
return 1;
case 'J':
if (add) {
channel->mode |= flag;
channel->joindelay = strtol(av[0], NULL, 0);
} else {
channel->mode &= ~flag;
channel->joindelay = 0;
}
return 1;
}
return 0;
}
/*************************************************************************/
/* We set +L for the user's language here--but only if they haven't already
* set it themselves. */
static int do_nick_identified(User *u, int old_status)
{
int mode_L, lang;
mode_L = u->mode & mode_char_to_flag('L', MODE_USER);
/* valid_ngi(u) _should_ always be true, but let's be paranoid */
if (valid_ngi(u) && u->ngi->language != LANG_DEFAULT)
lang = u->ngi->language;
else
lang = DEF_LANGUAGE;
if (is_oper(u) && is_services_admin(u)) {
if (!mode_L)
send_cmd(ServerName, "SVSMODE %s +aL %d", u->nick, langhash[lang]);
else
send_cmd(ServerName, "SVSMODE %s +a", u->nick);
} else {
if (!mode_L)
send_cmd(ServerName, "SVSMODE %s +L %d", u->nick, langhash[lang]);
}
return 0;
}
/*************************************************************************/
static int do_set_topic(const char *source, Channel *c, const char *topic,
const char *setter, time_t t)
{
if (setter)
return 0;
c->topic_time = t;
send_cmd(source, "TOPIC %s %s %ld :%s", c->name,
c->topic_setter, (long)c->topic_time, c->topic ? c->topic : "");
return 1;
}
/*************************************************************************/
static int do_check_modes(Channel *c, ChannelInfo *ci, int add, int32 flag)
{
if (add) {
switch (mode_flag_to_char(flag, MODE_CHANNEL)) {
case 'L':
if (!ci->mlock_link) {
module_log("warning: removing +L from channel %s mode lock"
" (missing parameter)", ci->name);
ci->mlock_on &= ~mode_char_to_flag('L', MODE_CHANNEL);
} else {
if (!c->link || irc_stricmp(ci->mlock_link, c->link) != 0)
set_cmode(s_ChanServ, c, "+L", ci->mlock_link);
}
return 1;
case 'f':
if (!ci->mlock_flood) {
module_log("warning: removing +f from channel %s mode lock"
" (missing parameter)", ci->name);
ci->mlock_on &= ~mode_char_to_flag('f', MODE_CHANNEL);
} else {
if (!c->flood || irc_stricmp(ci->mlock_flood, c->flood) != 0)
set_cmode(s_ChanServ, c, "+f", ci->mlock_flood);
}
return 1;
case 'J':
if (ci->mlock_joindelay <= 0) {
module_log("warning: removing +J from channel %s mode lock"
" (invalid parameter: %d)", ci->name,
ci->mlock_joindelay);
ci->mlock_on &= ~mode_char_to_flag('J', MODE_CHANNEL);
ci->mlock_joindelay = 0;
} else {
if (c->joindelay != ci->mlock_joindelay) {
char buf[32];
snprintf(buf, sizeof(buf), "%d", ci->mlock_joindelay);
set_cmode(s_ChanServ, c, "+J", buf);
}
}
return 1;
}
} else { /* remove */
switch (mode_flag_to_char(flag, MODE_CHANNEL)) {
case 'L':
/* Theoretically the channel can't exist, but send a set_cmode()
* just to be safe */
set_cmode(s_ChanServ, c, "-L");
return 1;
}
}
return 0;
}
/*************************************************************************/
static int do_set_mlock(User *u, ChannelInfo *ci, int mode, int add, char **av)
{
if (!mode) {
/* Final check of new mode lock--nothing to do */
return 0;
}
/* Single mode set/clear */
if (add) {
switch (mode) {
case 'L':
if (!valid_chan(av[0])) {
/* Invalid channel name */
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_LINK_BAD, mode);
return 1;
}
if (irc_stricmp(av[0], ci->name) == 0) {
/* Trying to link to the same channel */
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_LINK_SAME, mode);
return 1;
}
ci->mlock_link = sstrdup(av[0]);
break;
case 'f': {
char *s, *t;
/* Legal format for flood mode is "[*]digits:digits" */
s = av[0];
if (*s == '*')
s++;
t = strchr(s, ':');
if (t) {
t++;
if (s[strspn(s,"0123456789")] == ':'
&& t[strspn(t,"0123456789")] == 0
) {
/* String is valid */
ci->mlock_flood = sstrdup(av[0]);
break;
}
}
/* String is invalid */
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_BAD_PARAM, mode);
return 1;
} /* case 'f' */
case 'J':
ci->mlock_joindelay = atol(av[0]);
if (ci->mlock_joindelay <= 0) {
notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_NEED_POSITIVE, 'J');
return 1;
}
break;
} /* switch (mode) */
} else { /* !add -> lock off */
switch (mode) {
case 'L': {
/* The channel should not exist here; check anyway just to be
* safe, and do a set_cmode() or send_cmd() as appropriate. */
Channel *c = get_channel(ci->name);
if (c)
set_cmode(s_ChanServ, c, "-L");
else
send_cmd(s_ChanServ, "MODE %s -L", ci->name);
} /* case 'L' */
case 'f':
free(ci->mlock_flood);
ci->mlock_flood = NULL;
break;
case 'J':
ci->mlock_joindelay = 0;
break;
} /* switch (mode) */
} /* if (add) */
return 0;
}
/*************************************************************************/
static int do_send_akill(const char *username, const char *host,
time_t expires, const char *who, const char *reason)
{
time_t now = time(NULL);
send_cmd(ServerName, "AKILL %s %s %ld %s %ld :%s", host, username,
(long)((expires && expires > now) ? expires - now : 0),
who ? who : "<unknown>", (long)now, reason);
return 1;
}
/*************************************************************************/
static int do_cancel_akill(const char *username, const char *host)
{
send_cmd(ServerName, "RAKILL %s %s", host, username);
return 1;
}
/*************************************************************************/
static int do_send_exclude(const char *username, const char *host,
time_t expires, const char *who, const char *reason)
{
time_t now = time(NULL);
send_cmd(ServerName, "EXCLUDE %s %s %ld %s %ld :%s", host, username,
(long)((expires && expires > now) ? expires - now : 0),
who ? who : "<unknown>", (long)now, reason);
return 1;
}
/*************************************************************************/
static int do_cancel_exclude(const char *username, const char *host)
{
send_cmd(ServerName, "REXCLUDE %s %s", host, username);
return 1;
}
/*************************************************************************/
static int do_send_sgline(const char *mask, time_t expires, const char *who,
const char *reason)
{
send_cmd(ServerName, "SGLINE %d :%s:%s", (int)strlen(mask), mask, reason);
return 1;
}
static int do_send_sqline(const char *mask, time_t expires, const char *who,
const char *reason)
{
send_cmd(ServerName, "SQLINE %s :%s", mask, reason);
return 1;
}
static int do_send_szline(const char *mask, time_t expires, const char *who,
const char *reason)
{
send_cmd(ServerName, "SZLINE %s :%s", mask, reason);
return 1;
}
/*************************************************************************/
static int do_cancel_sgline(const char *mask)
{
send_cmd(ServerName, "UNSGLINE :%s", mask);
return 1;
}
static int do_cancel_sqline(const char *mask)
{
send_cmd(ServerName, "UNSQLINE %s", mask);
return 1;
}
static int do_cancel_szline(const char *mask)
{
send_cmd(ServerName, "UNSZLINE %s", mask);
return 1;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
const int32 module_version = MODULE_VERSION_CODE;
ConfigDirective module_config[] = {
{ "NetworkDomain", { { CD_STRING, 0, &NetworkDomain } } },
SJOIN_CONFIG,
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "operserv/main") == 0) {
module_operserv = mod;
p_is_services_admin = get_module_symbol(mod, "is_services_admin");
if (!p_is_services_admin) {
module_log("warning: unable to look up symbol `is_services_admin'"
" in module `operserv/main'");
}
} else if (strcmp(modname, "operserv/akill") == 0) {
if (!add_callback(mod, "send_akill", do_send_akill))
module_log("Unable to add send_akill callback");
if (!add_callback(mod, "send_exclude", do_send_exclude))
module_log("Unable to add send_exclude callback");
if (!add_callback(mod, "cancel_akill", do_cancel_akill))
module_log("Unable to add cancel_akill callback");
if (!add_callback(mod, "cancel_exclude", do_cancel_exclude))
module_log("Unable to add cancel_exclude callback");
} else if (strcmp(modname, "operserv/sline") == 0) {
if (!add_callback(mod, "send_sgline", do_send_sgline))
module_log("Unable to add send_sgline callback");
if (!add_callback(mod, "send_sqline", do_send_sqline))
module_log("Unable to add send_sqline callback");
if (!add_callback(mod, "send_szline", do_send_szline))
module_log("Unable to add send_szline callback");
if (!add_callback(mod, "cancel_sgline", do_cancel_sgline))
module_log("Unable to add cancel_sgline callback");
if (!add_callback(mod, "cancel_sqline", do_cancel_sqline))
module_log("Unable to add cancel_sqline callback");
if (!add_callback(mod, "cancel_szline", do_cancel_szline))
module_log("Unable to add cancel_szline callback");
} else if (strcmp(modname, "nickserv/main") == 0) {
if (!add_callback(mod, "identified", do_nick_identified))
module_log("Unable to add NickServ identified callback");
} else if (strcmp(modname, "chanserv/main") == 0) {
module_chanserv = mod;
p_s_ChanServ = get_module_symbol(mod, "s_ChanServ");
if (!p_s_ChanServ) {
module_log("Unable to resolve symbol `s_ChanServ' in module"
" `chanserv/main'");
p_s_ChanServ = &ServerName;
}
/* first/next_channelinfo are defined in the database module */
p_first_channelinfo = get_module_symbol(NULL, "first_channelinfo");
if (!p_first_channelinfo)
module_log("Unable to resolve symbol `first_channelinfo'");
p_next_channelinfo = get_module_symbol(NULL, "next_channelinfo");
if (!p_next_channelinfo)
module_log("Unable to resolve symbol `next_channelinfo'");
if (!add_callback(mod, "check_modes", do_check_modes))
module_log("Unable to add ChanServ check_modes callback");
if (!add_callback(mod, "SET MLOCK", do_set_mlock))
module_log("Unable to add ChanServ SET MLOCK callback");
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_operserv) {
module_operserv = NULL;
p_is_services_admin = NULL;
} else if (mod == module_chanserv) {
module_chanserv = NULL;
p_s_ChanServ = &ServerName;
p_first_channelinfo = NULL;
p_next_channelinfo = NULL;
remove_callback(mod, "SET MLOCK", do_set_mlock);
remove_callback(mod, "check_modes", do_check_modes);
}
return 0;
}
/*************************************************************************/
int init_module(Module *module_)
{
unsigned char c;
module = module_;
protocol_name = "trircd";
protocol_version = "5.5+";
protocol_features = PF_SZLINE | PF_SVSJOIN | PF_AKILL_EXCL | PF_HALFOP
| PF_NOQUIT;
protocol_nickmax = 30;
if (!register_messages(trircd_messages)) {
module_log("Unable to register messages");
exit_module(1);
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, "receive message", do_receive_message)
|| !add_callback(NULL, "user create", do_user_create)
|| !add_callback(NULL, "channel MODE", do_channel_mode)
|| !add_callback(NULL, "user servicestamp change",
do_user_servicestamp_change)
|| !add_callback(NULL, "user MODE", do_user_mode)
|| !add_callback(NULL, "set topic", do_set_topic)
) {
module_log("Unable to add callbacks");
exit_module(1);
return 0;
}
if (!init_banexcept(module)
|| !init_chanprot(module)
|| !init_halfop(module)
|| !init_invitemask(module)
|| !init_sjoin(module)
|| !init_svsnick(module)
|| !init_token(module, trircd5_tokens)
) {
exit_module(1);
return 0;
}
init_modes();
init_langhash();
irc_lowertable['['] = '[';
irc_lowertable['\\'] = '\\';
irc_lowertable[']'] = ']';
for (c = 'A'; c <= '}'; c++)
valid_nick_table[c] = 3;
for (c = 0; c < 32; c++)
valid_chan_table[c] = 0;
valid_chan_table[160] = 0;
send_nick = do_send_nick;
send_nickchange = do_send_nickchange;
send_namechange = do_send_namechange;
send_server = do_send_server;
send_server_remote = do_send_server_remote;
wallops = do_wallops;
notice_all = do_notice_all;
send_channel_cmd = do_send_channel_cmd;
pseudoclient_modes = "";
enforcer_modes = "";
setstring(OPER_BOUNCY_MODES, OPER_BOUNCY_MODES_U_LINE);
return 1;
}
/*************************************************************************/
int exit_module(int shutdown)
{
if (!shutdown) {
/* Do not allow removal */
return 0;
}
exit_token();
exit_svsnick();
exit_sjoin();
exit_invitemask();
exit_halfop();
exit_chanprot();
exit_banexcept();
remove_callback(NULL, "set topic", do_set_topic);
remove_callback(NULL, "user MODE", do_user_mode);
remove_callback(NULL, "user servicestamp change",
do_user_servicestamp_change);
remove_callback(NULL, "channel MODE", do_channel_mode);
remove_callback(NULL, "user create", do_user_create);
remove_callback(NULL, "receive message", do_receive_message);
remove_callback(NULL, "connect", do_connect);
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
unregister_messages(trircd_messages);
return 1;
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1