/* Definitions of IRC message functions and list of messages. * * IRC Services is copyright (c) 1996-2007 Andrew Church. * E-mail: * 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); } /*************************************************************************/