/* Definitions of IRC message functions and list of messages.
*
* 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 "messages.h"
#include "language.h"
#include "modules.h"
#include "ignore.h"
#include "version.h"
/*************************************************************************/
/* Callbacks for various messages */
static int cb_privmsg = -1;
static int cb_whois = -1;
/*************************************************************************/
/************************ Basic message handling *************************/
/*************************************************************************/
static void m_nickcoll(char *source, int ac, char **av)
{
if (ac < 1)
return;
if (!readonly)
introduce_user(av[0]);
}
/*************************************************************************/
static void m_ping(char *source, int ac, char **av)
{
if (ac < 1)
return;
send_cmd(ServerName, "PONG %s %s", ac>1 ? av[1] : ServerName, av[0]);
}
/*************************************************************************/
static void m_info(char *source, int ac, char **av)
{
int i;
struct tm *tm;
char timebuf[64];
tm = localtime(&start_time);
strftime(timebuf, sizeof(timebuf), "%a %b %d %H:%M:%S %Y %Z", tm);
for (i = 0; info_text[i]; i++)
send_cmd(ServerName, "371 %s :%s", source, info_text[i]);
send_cmd(ServerName, "371 %s :Version %s (%s)", source,
version_number, version_build);
send_cmd(ServerName, "371 %s :On-line since %s", source, timebuf);
send_cmd(ServerName, "374 %s :End of /INFO list.", source);
}
/*************************************************************************/
static void m_join(char *source, int ac, char **av)
{
if (ac != 1)
return;
do_join(source, ac, av);
}
/*************************************************************************/
static void m_kick(char *source, int ac, char **av)
{
if (ac != 3)
return;
do_kick(source, ac, av);
}
/*************************************************************************/
static void m_kill(char *source, int ac, char **av)
{
if (ac != 2)
return;
/* Recover if someone kills us. If introduce_user() returns 0, then
* the user in question isn't a pseudoclient, so pass it on to the
* user handling code. */
if (!introduce_user(av[0]))
do_kill(source, ac, av);
}
/*************************************************************************/
static void m_mode(char *source, int ac, char **av)
{
if (*av[0] == '#' || *av[0] == '&') {
if (ac < 2)
return;
do_cmode(source, ac, av);
} else {
if (ac != 2) {
return;
} else if (irc_stricmp(source,av[0])!=0 && strchr(source,'.')==NULL) {
log("user: MODE %s %s from different nick %s!", av[0], av[1],
source);
wallops(NULL, "%s attempted to change mode %s for %s",
source, av[1], av[0]);
return;
}
do_umode(source, ac, av);
}
}
/*************************************************************************/
static void m_motd(char *source, int ac, char **av)
{
FILE *f;
char buf[BUFSIZE];
f = fopen(MOTDFilename, "r");
send_cmd(ServerName, "375 %s :- %s Message of the Day",
source, ServerName);
if (f) {
while (fgets(buf, sizeof(buf), f)) {
buf[strlen(buf)-1] = 0;
send_cmd(ServerName, "372 %s :- %s", source, buf);
}
fclose(f);
} else {
send_cmd(ServerName, "372 %s :- MOTD file not found! Please "
"contact your IRC administrator.", source);
}
}
/*************************************************************************/
static void m_part(char *source, int ac, char **av)
{
if (ac < 1 || ac > 2)
return;
do_part(source, ac, av);
}
/*************************************************************************/
static const char msg_up_inactive[] =
"Network buffer size exceeded inactive threshold (%d%%), not processing"
" PRIVMSGs";
static const char msg_up_ignore[] =
"Network buffer size exceeded ignore threshold (%d%%), ignoring PRIVMSGs";
static const char msg_down_inactive[] =
"Network buffer size dropped below ignore threshold (%d%%), not"
" processing PRIVMSGs";
static const char msg_down_normal[] =
"Network buffer size dropped below inactive threshold (%d%%),"
" processing PRIVMSGs normally";
static void m_privmsg(char *source, int ac, char **av)
{
/* PRIVMSG handling status based on NetBufferLimit settings */
static enum {NORMAL,INACTIVE,IGNORE} netbuf_status = NORMAL;
struct timeval start, stop; /* When processing started and finished */
User *u = get_user(source);
char *s;
if (ac != 2)
return;
/* If a server is specified (nick@server format), make sure it matches
* us, and strip it off. */
s = strchr(av[0], '@');
if (s) {
*s++ = 0;
if (stricmp(s, ServerName) != 0)
return;
}
/* Check network buffer status. */
if (NetBufferLimitInactive) {
int bufstat = sock_bufstat(servsock, NULL, NULL, NULL, NULL);
const char *message = NULL;
int value = 0;
switch (netbuf_status) {
case NORMAL:
if (NetBufferLimitIgnore && bufstat >= NetBufferLimitIgnore) {
message = msg_up_ignore;
value = NetBufferLimitIgnore;
netbuf_status = IGNORE;
} else if (bufstat >= NetBufferLimitInactive) {
message = msg_up_inactive;
value = NetBufferLimitInactive;
netbuf_status = INACTIVE;
}
break;
case INACTIVE:
if (NetBufferLimitIgnore && bufstat >= NetBufferLimitIgnore) {
message = msg_up_ignore;
value = NetBufferLimitIgnore;
netbuf_status = IGNORE;
} else if (bufstat < NetBufferLimitInactive) {
message = msg_down_normal;
value = NetBufferLimitInactive;
netbuf_status = NORMAL;
}
break;
case IGNORE:
if (bufstat < NetBufferLimitInactive) {
message = msg_down_normal;
value = NetBufferLimitInactive;
netbuf_status = NORMAL;
} else if (bufstat < NetBufferLimitIgnore) {
message = msg_down_inactive;
value = NetBufferLimitIgnore;
netbuf_status = INACTIVE;
}
break;
} /* switch (netbuf_status) */
if (message) {
log(message, value);
wallops(NULL, message, value);
}
}
/* Check if we should ignore. Operators always get through. */
if (!is_oper(u)) {
if (netbuf_status != NORMAL) {
if (netbuf_status == INACTIVE) {
if (u)
notice_lang(av[0], u, SERVICES_IS_BUSY);
else
notice(av[0], source, getstring(NULL,SERVICES_IS_BUSY));
}
return;
} else if (allow_ignore) {
IgnoreData *ign = get_ignore(source);
if (ign && ign->time > time(NULL)) {
log("Ignored message from %s: \"%s\"", source, inbuf);
return;
}
}
}
gettimeofday(&start, NULL);
call_callback_3(NULL, cb_privmsg, source, av[0], av[1]);
gettimeofday(&stop, NULL);
/* Add to ignore list if the command took a significant amount of time. */
if (!is_oper(u) && allow_ignore) {
int32 diff;
diff = (stop.tv_sec-start.tv_sec)*1000000+(stop.tv_usec-start.tv_usec);
if (diff >= 1000000 && *source && !strchr(source, '.'))
add_ignore(source, diff/1000000);
}
}
/*************************************************************************/
static void m_quit(char *source, int ac, char **av)
{
if (ac != 1)
return;
do_quit(source, ac, av);
}
/*************************************************************************/
static void m_server(char *source, int ac, char **av)
{
do_server(source, ac, av);
}
/*************************************************************************/
static void m_squit(char *source, int ac, char **av)
{
do_squit(source, ac, av);
}
/*************************************************************************/
static void m_stats(char *source, int ac, char **av)
{
if (ac < 1)
return;
switch (*av[0]) {
case 'u': {
int uptime = time(NULL) - start_time;
send_cmd(NULL, "242 %s :Services up %d day%s, %02d:%02d:%02d",
source, uptime/86400, (uptime/86400 == 1) ? "" : "s",
(uptime/3600) % 24, (uptime/60) % 60, uptime % 60);
send_cmd(NULL, "250 %s :Current users: %d (%d ops); maximum %d",
source, usercnt, opcnt, maxusercnt);
send_cmd(NULL, "219 %s u :End of /STATS report.", source);
break;
} /* case 'u' */
case 'l': {
uint32 read_kb, written_kb;
sock_rwstat(servsock, &read_kb, &written_kb);
send_cmd(NULL, "211 %s Server SendBuf SentBytes SentMsgs RecvBuf "
"RecvBytes RecvMsgs ConnTime", source);
send_cmd(NULL, "211 %s %s %u %u %d %u %u %d %ld", source, RemoteServer,
read_buffer_len(servsock), read_kb*1024, -1,
write_buffer_len(servsock), written_kb*1024, -1,
(long)start_time);
send_cmd(NULL, "219 %s l :End of /STATS report.", source);
break;
}
case 'c':
case 'h':
case 'i':
case 'k':
case 'm':
case 'o':
case 'y':
send_cmd(NULL, "219 %s %c :/STATS %c not applicable or not supported.",
source, *av[0], *av[0]);
break;
}
}
/*************************************************************************/
static void m_time(char *source, int ac, char **av)
{
time_t t;
struct tm *tm;
char buf[64];
time(&t);
tm = localtime(&t);
strftime(buf, sizeof(buf), "%a %b %d %H:%M:%S %Y %Z", tm);
send_cmd(NULL, "391 %s %s :%s", source, ServerName, buf);
}
/*************************************************************************/
static void m_topic(char *source, int ac, char **av)
{
if (ac != 4)
return;
do_topic(source, ac, av);
}
/*************************************************************************/
static void m_version(char *source, int ac, char **av)
{
if (source)
send_cmd(ServerName, "351 %s %s-%s %s :%s", source,
program_name, version_number, ServerName, version_build);
}
/*************************************************************************/
static void m_whois(char *source, int ac, char **av)
{
if (source && ac >= 1) {
if (call_callback_3(NULL, cb_whois, source, av[0],
ac>1 ? av[1] : NULL) <= 0
) {
send_cmd(ServerName, "401 %s %s :No such service.", source, av[0]);
}
}
}
/*************************************************************************/
/* Basic messages (defined above). Note that NICK and USER are left to the
* protocol modules, since their usage varies widely between protocols. */
static Message base_messages[] = {
{ "401", NULL },
{ "436", m_nickcoll },
{ "AWAY", NULL },
{ "INFO", m_info },
{ "JOIN", m_join },
{ "KICK", m_kick },
{ "KILL", m_kill },
{ "MODE", m_mode },
{ "MOTD", m_motd },
{ "NOTICE", NULL },
{ "PART", m_part },
{ "PASS", NULL },
{ "PING", m_ping },
{ "PONG", NULL },
{ "PRIVMSG", m_privmsg },
{ "QUIT", m_quit },
{ "SERVER", m_server },
{ "SQUIT", m_squit },
{ "STATS", m_stats },
{ "TIME", m_time },
{ "TOPIC", m_topic },
{ "VERSION", m_version },
{ "WALLOPS", NULL },
{ "WHOIS", m_whois },
{ NULL }
};
/*************************************************************************/
/******************** Message registration and lookup ********************/
/*************************************************************************/
/* Structure to link tables together */
typedef struct messagetable_ MessageTable;
struct messagetable_ {
MessageTable *next, *prev;
Message *table;
};
static MessageTable *msgtable;
/*************************************************************************/
/*************************************************************************/
/* Register the given table of messages. Returns 1 on success, 0 on
* failure (`table' == NULL, `table' already registered, or out of memory).
*/
int register_messages(Message *table)
{
MessageTable *mt;
if (!table)
return 0;
LIST_SEARCH_SCALAR(msgtable, table, table, mt);
if (mt) /* if it's already on the list, abort */
return 0;
mt = malloc(sizeof(*mt));
if (!mt) /* out of memory */
return 0;
mt->table = table;
LIST_INSERT(mt, msgtable);
return 1;
}
/*************************************************************************/
/* Unregister the given table of messages. Returns 1 on success, 0 on
* failure (`table' not registered).
*/
int unregister_messages(Message *table)
{
MessageTable *mt;
LIST_SEARCH_SCALAR(msgtable, table, table, mt);
if (!mt)
return 0;
LIST_REMOVE(mt, msgtable);
free(mt);
return 1;
}
/*************************************************************************/
/* Return the Message structure for the given message name, or NULL if none
* exists. If there are multiple tables with entries for the message,
* returns the entry in the most recently registered table.
*/
Message *find_message(const char *name)
{
MessageTable *mt;
Message *m;
LIST_FOREACH (mt, msgtable) {
for (m = mt->table; m->name; m++) {
if (stricmp(name, m->name) == 0)
return m;
}
}
return NULL;
}
/*************************************************************************/
/************************ Initialization/cleanup *************************/
/*************************************************************************/
int messages_init(int ac, char **av)
{
if (!register_messages(base_messages)) {
log("messages_init: Unable to register base messages\n");
return 0;
}
cb_privmsg = register_callback(NULL, "m_privmsg");
cb_whois = register_callback(NULL, "m_whois");
if (cb_privmsg < 0 || cb_whois < 0) {
log("messages_init: register_callback() failed\n");
return 0;
}
return 1;
}
/*************************************************************************/
void messages_cleanup(void)
{
unregister_callback(NULL, cb_whois);
unregister_callback(NULL, cb_privmsg);
unregister_messages(base_messages);
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1