/* Statistics generation (StatServ) main module. * Based on code (c) Andrew Kempe (TheShadow) * E-mail: * * 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 "modules.h" #include "conffile.h" #include "language.h" #include "commands.h" #include "modules/operserv/operserv.h" #include "statserv.h" /*************************************************************************/ static Module *module; static Module *module_nickserv; static int cb_command = -1; static int cb_help = -1; static int cb_help_cmds = -1; static int db_opened = 0; char *s_StatServ; static char *desc_StatServ; static char *StatDBName; static int SSOpersOnly; static int16 servercnt = 0; /* Number of online servers */ /*************************************************************************/ static void do_help(User *u); static void do_servers(User *u); static void do_users(User *u); /*************************************************************************/ static Command cmds[] = { { "HELP", do_help, NULL, -1, -1,-1 }, { "SERVERS", do_servers, NULL, -1, STAT_HELP_SERVERS, STAT_OPER_HELP_SERVERS }, { "USERS", do_users, NULL, STAT_HELP_USERS, -1,-1 }, { NULL } }; /*************************************************************************/ /****************************** Statistics *******************************/ /*************************************************************************/ /* Introduce the StatServ pseudoclient. */ static int introduce_statserv(const char *nick) { if (!nick || irc_stricmp(nick, s_StatServ) == 0) { char modebuf[BUFSIZE]; snprintf(modebuf, sizeof(modebuf), "i%s", pseudoclient_modes); send_nick(s_StatServ, ServiceUser, ServiceHost, ServerName, desc_StatServ, modebuf); return nick ? 1 : 0; } return 0; } /*************************************************************************/ /* Return a help message. */ static void do_help(User *u) { char *cmd = strtok_remaining(); if (!cmd) { notice_help(s_StatServ, u, STAT_HELP, s_StatServ); } else if (stricmp(cmd, "COMMANDS") == 0) { notice_help(s_StatServ, u, STAT_HELP_COMMANDS); call_callback_2(module, cb_help_cmds, u, 0); } else if (call_callback_2(module, cb_help, u, cmd) > 0) { return; } else { help_cmd(s_StatServ, u, module, cmd); } } /*************************************************************************/ /* Main StatServ routine. */ static int statserv(const char *source, const char *target, char *buf) { const char *cmd; const char *s; User *u; if (irc_stricmp(target, s_StatServ) != 0) return 0; u = get_user(source); if (!u) { module_log("user record for %s not found", source); notice(s_StatServ, source, getstring(NULL,INTERNAL_ERROR)); return 1; } if (SSOpersOnly && !is_oper(u)) { notice_lang(s_StatServ, u, ACCESS_DENIED); return 1; } cmd = strtok(buf, " "); if (!cmd) { return 1; } else if (stricmp(cmd, "\1PING") == 0) { if (!(s = strtok(NULL, ""))) s = "\1"; notice(s_StatServ, source, "\1PING %s", s); } else { if (call_callback_2(module, cb_command, u, cmd) <= 0) run_cmd(s_StatServ, u, module, cmd); } return 1; } /*************************************************************************/ /* Return a /WHOIS response for StatServ. */ static int statserv_whois(const char *source, char *who, char *extra) { if (irc_stricmp(who, s_StatServ) != 0) return 0; send_cmd(ServerName, "311 %s %s %s %s * :%s", source, who, ServiceUser, ServiceHost, desc_StatServ); send_cmd(ServerName, "312 %s %s %s :%s", source, who, ServerName, ServerDesc); send_cmd(ServerName, "313 %s %s :is a network service", source, who); send_cmd(ServerName, "318 %s %s End of /WHOIS response.", source, who); return 1; } /*************************************************************************/ /* Callback for NickServ REGISTER/LINK check; we disallow * registration/linking of the StatServ pseudoclient nickname. */ static int do_reglink_check(const User *u, const char *nick, const char *pass, const char *email) { return irc_stricmp(nick, s_StatServ) == 0; } /*************************************************************************/ /************************* Server info display ***************************/ /*************************************************************************/ static void do_servers(User *u) { ServerStats *ss; const char *cmd = strtok(NULL, " "); char *mask = strtok(NULL, " "); int count = 0, nservers = 0; if (!cmd) cmd = ""; if (stricmp(cmd, "STATS") == 0) { ServerStats *ss_lastquit = NULL; int onlinecount = 0; char lastquit_buf[BUFSIZE]; for (ss = first_serverstats(); ss; ss = next_serverstats()) { nservers++; if (ss->t_quit > 0 && (!ss_lastquit || ss->t_quit > ss_lastquit->t_quit)) ss_lastquit = ss; if (ss->t_join > ss->t_quit) onlinecount++; } notice_lang(s_StatServ, u, STAT_SERVERS_STATS_TOTAL, nservers); notice_lang(s_StatServ, u, STAT_SERVERS_STATS_ON_OFFLINE, onlinecount, (onlinecount*100)/nservers, nservers-onlinecount, ((nservers-onlinecount)*100)/nservers); if (ss_lastquit) { strftime_lang(lastquit_buf, sizeof(lastquit_buf), u->ngi, STRFTIME_DATE_TIME_FORMAT, ss_lastquit->t_quit); notice_lang(s_StatServ, u, STAT_SERVERS_LASTQUIT_WAS, ss_lastquit->name, lastquit_buf); } } else if (stricmp(cmd, "LIST") == 0) { int matchcount = 0; notice_lang(s_StatServ, u, STAT_SERVERS_LIST_HEADER); for (ss = first_serverstats(); ss; ss = next_serverstats()) { if (mask && !match_wild_nocase(mask, ss->name)) continue; matchcount++; if (ss->t_join < ss->t_quit) continue; count++; notice_lang(s_StatServ, u, STAT_SERVERS_LIST_FORMAT, ss->name, ss->usercnt, !usercnt ? 0 : (ss->usercnt*100)/usercnt, ss->opercnt, !opcnt ? 0 : (ss->opercnt*100)/opcnt); } notice_lang(s_StatServ, u, STAT_SERVERS_LIST_RESULTS, count, matchcount); } else if (stricmp(cmd, "VIEW") == 0) { char *param = strtok(NULL, " "); char join_buf[BUFSIZE]; char quit_buf[BUFSIZE]; int is_online; int limitto = 0; /* 0 == none; 1 == online; 2 == offline */ if (param) { if (stricmp(param, "ONLINE") == 0) { limitto = 1; } else if (stricmp(param, "OFFLINE") == 0) { limitto = 2; } } for (ss = first_serverstats(); ss; ss = next_serverstats()) { nservers++; if (mask && !match_wild_nocase(mask, ss->name)) continue; if (ss->t_join >= ss->t_quit) is_online = 1; else is_online = 0; if (limitto && !((is_online && limitto == 1) || (!is_online && limitto == 2))) continue; count++; strftime_lang(join_buf, sizeof(join_buf), u->ngi, STRFTIME_DATE_TIME_FORMAT, ss->t_join); if (ss->t_quit != 0) { strftime_lang(quit_buf, sizeof(quit_buf), u->ngi, STRFTIME_DATE_TIME_FORMAT, ss->t_quit); } notice_lang(s_StatServ, u, is_online ? STAT_SERVERS_VIEW_HEADER_ONLINE : STAT_SERVERS_VIEW_HEADER_OFFLINE, ss->name); notice_lang(s_StatServ, u, STAT_SERVERS_VIEW_LASTJOIN, join_buf); if (ss->t_quit > 0) notice_lang(s_StatServ, u, STAT_SERVERS_VIEW_LASTQUIT, quit_buf); if (ss->quit_message) notice_lang(s_StatServ, u, STAT_SERVERS_VIEW_QUITMSG, ss->quit_message); if (is_online) notice_lang(s_StatServ, u, STAT_SERVERS_VIEW_USERS_OPERS, ss->usercnt, !usercnt ? 0 : (ss->usercnt*100)/usercnt, ss->opercnt, !opcnt ? 0 : (ss->opercnt*100)/opcnt); } notice_lang(s_StatServ, u, STAT_SERVERS_VIEW_RESULTS, count, nservers); } else if (!is_services_admin(u)) { if (is_oper(u)) notice_lang(s_StatServ, u, PERMISSION_DENIED); else syntax_error(s_StatServ, u, "SERVERS", STAT_SERVERS_SYNTAX); /* Only Services admins have access from here on! */ } else if (stricmp(cmd, "DELETE") == 0) { if (!mask) { syntax_error(s_StatServ, u, "SERVERS", STAT_SERVERS_DELETE_SYNTAX); } else if (!(ss = get_serverstats(mask))) { notice_lang(s_StatServ, u, SERV_X_NOT_FOUND, mask); } else if (ss->t_join > ss->t_quit) { notice_lang(s_StatServ, u, STAT_SERVERS_REMOVE_SERV_FIRST, mask); } else { del_serverstats(ss); notice_lang(s_StatServ, u, STAT_SERVERS_DELETE_DONE, mask); } } else if (stricmp(cmd, "COPY") == 0) { char *newname = strtok(NULL, " "); if (!newname) { syntax_error(s_StatServ, u, "SERVERS", STAT_SERVERS_COPY_SYNTAX); } else if (!(ss = get_serverstats(mask))) { notice_lang(s_StatServ, u, SERV_X_NOT_FOUND, mask); } else if (get_serverstats(newname)) { notice_lang(s_StatServ, u, STAT_SERVERS_SERVER_EXISTS, newname); } else { ss = new_serverstats(newname); add_serverstats(ss); notice_lang(s_StatServ, u, STAT_SERVERS_COPY_DONE, mask, newname); } } else if (stricmp(cmd, "RENAME") == 0) { char *newname = strtok(NULL, " "); ServerStats *newss; if (!newname) { syntax_error(s_StatServ, u, "SERVERS", STAT_SERVERS_RENAME_SYNTAX); } else if (!(ss = get_serverstats(mask))) { notice_lang(s_StatServ, u, SERV_X_NOT_FOUND, mask); } else if (ss->t_join > ss->t_quit) { notice_lang(s_StatServ, u, STAT_SERVERS_REMOVE_SERV_FIRST, mask); } else if (get_serverstats(newname)) { notice_lang(s_StatServ, u, STAT_SERVERS_SERVER_EXISTS, newname); } else { newss = new_serverstats(newname); newss->usercnt = ss->usercnt; newss->opercnt = ss->opercnt; newss->t_join = ss->t_join; newss->t_quit = ss->t_quit; newss->quit_message = ss->quit_message; ss->quit_message = NULL; del_serverstats(ss); add_serverstats(newss); notice_lang(s_StatServ, u, STAT_SERVERS_RENAME_DONE, mask,newname); } } else { syntax_error(s_StatServ, u, "SERVERS", STAT_SERVERS_SYNTAX); } } /*************************************************************************/ static void do_users(User *u) { const char *cmd = strtok(NULL, " "); int avgusers, avgopers; if (!cmd) cmd = ""; if (stricmp(cmd, "STATS") == 0) { notice_lang(s_StatServ, u, STAT_USERS_TOTUSERS, usercnt); notice_lang(s_StatServ, u, STAT_USERS_TOTOPERS, opcnt); avgusers = (usercnt + servercnt/2) / servercnt; avgopers = (opcnt*10 + servercnt/2) / servercnt; notice_lang(s_StatServ, u, STAT_USERS_SERVUSERS, avgusers); notice_lang(s_StatServ, u, STAT_USERS_SERVOPERS, avgopers/10, avgopers%10); } else { syntax_error(s_StatServ, u, "USERS", STAT_USERS_SYNTAX); } } /*************************************************************************/ /******************** ServerStats new/free (global) **********************/ /*************************************************************************/ /* Create a new ServerStats structure for the given server name and return * it. Always successful. */ EXPORT_FUNC(new_serverstats) ServerStats *new_serverstats(const char *servername) { ServerStats *ss; ss = scalloc(sizeof(*ss), 1); ss->name = sstrdup(servername); return ss; } /*************************************************************************/ /* Free a ServerStats structure and associated data. */ EXPORT_FUNC(free_serverstats) void free_serverstats(ServerStats *ss) { free(ss->name); free(ss->quit_message); free(ss); } /*************************************************************************/ /************************** Callback routines ****************************/ /*************************************************************************/ /* Handle a server joining */ static int stats_do_server(Server *server) { ServerStats *ss; servercnt++; ss = get_serverstats(server->name); if (ss) { /* Server has rejoined us */ ss->usercnt = 0; ss->opercnt = 0; ss->t_join = time(NULL); ss->locked = 1; put_serverstats(ss); } else { /* Totally new server */ ss = new_serverstats(server->name); /* cleared to zero */ ss->t_join = time(NULL); ss->locked = 1; add_serverstats(ss); } server->stats = ss; return 0; } /*************************************************************************/ /* Handle a server quitting */ static int stats_do_squit(Server *server, const char *quit_message) { ServerStats *ss = server->stats; servercnt--; ss->t_quit = time(NULL); free(ss->quit_message); ss->quit_message = *quit_message ? sstrdup(quit_message) : NULL; ss->locked = 0; put_serverstats(ss); return 0; } /*************************************************************************/ /* Handle a user joining */ static int stats_do_newuser(User *user) { if (user->server) user->server->stats->usercnt++; return 0; } /*************************************************************************/ /* Handle a user quitting */ static int stats_do_quit(User *user) { if (user->server) { ServerStats *ss = user->server->stats; if (!ss) { module_log("BUG! no serverstats for %s in do_quit(%s)", user->server->name, user->nick); return 0; } ss->usercnt--; if (is_oper(user)) ss->opercnt--; } return 0; } /*************************************************************************/ /* Handle a user mode change */ static int stats_do_umode(User *user, int modechar, int add) { if (user->server) { if (modechar == 'o') { ServerStats *ss = user->server->stats; if (!ss) { module_log("BUG! no serverstats for %s in do_quit(%s)", user->server->name, user->nick); return 0; } if (add) ss->opercnt++; else ss->opercnt--; } } return 0; } /*************************************************************************/ /* Save StatServ data */ static int do_save_data() { sync_statserv_db(StatDBName); return 0; } /*************************************************************************/ /**************************** Module functions ***************************/ /*************************************************************************/ const int32 module_version = MODULE_VERSION_CODE; ConfigDirective module_config[] = { { "SSOpersOnly", { { CD_SET, 0, &SSOpersOnly } } }, { "StatServDB", { { CD_STRING, CF_DIRREQ, &StatDBName } } }, { "StatServName", { { CD_STRING, CF_DIRREQ, &s_StatServ }, { CD_STRING, 0, &desc_StatServ } } }, { NULL } }; /*************************************************************************/ static int do_load_module(Module *mod, const char *modname) { if (strcmp(modname, "nickserv/main") == 0) { module_nickserv = mod; if (!add_callback(mod, "REGISTER/LINK check", do_reglink_check)) module_log("Unable to register NickServ REGISTER/LINK check" " callback"); } return 0; } /*************************************************************************/ static int do_unload_module(Module *mod) { if (mod == module_nickserv) { remove_callback(module_nickserv, "REGISTER/LINK check", do_reglink_check); module_nickserv = NULL; } return 0; } /*************************************************************************/ static int do_reconfigure(int after_configure) { static char old_s_StatServ[NICKMAX]; static char *old_desc_StatServ = NULL; static char *old_StatDBName = NULL; if (!after_configure) { /* Before reconfiguration: save old values. */ strscpy(old_s_StatServ, s_StatServ, NICKMAX); old_desc_StatServ = strdup(desc_StatServ); old_StatDBName = strdup(StatDBName); } else { /* After reconfiguration: handle value changes. */ if (strcmp(old_s_StatServ, s_StatServ) != 0) send_nickchange(old_s_StatServ, s_StatServ); if (!old_desc_StatServ || strcmp(old_desc_StatServ,desc_StatServ) != 0) send_namechange(s_StatServ, desc_StatServ); if (!old_StatDBName || strcmp(old_StatDBName, StatDBName) != 0) { module_log("reconfigure: new database name will only take" " effect after restart"); /* Restore the old database name */ free(StatDBName); StatDBName = old_StatDBName; /* Make sure the old name isn't freed below */ old_StatDBName = NULL; } free(old_desc_StatServ); free(old_StatDBName); } /* if (!after_configure) */ return 0; } /*************************************************************************/ int init_module(Module *module_) { Module *tmpmod; module = module_; if (!new_commandlist(module) || !register_commands(module, cmds)) { module_log("Unable to register commands"); exit_module(0); return 0; } cb_command = register_callback(module, "command"); cb_help = register_callback(module, "HELP"); cb_help_cmds = register_callback(module, "HELP COMMANDS"); if (cb_command < 0 || cb_help < 0 || cb_help_cmds < 0) { module_log("Unable to register callbacks"); exit_module(0); return 0; } if (!add_callback(NULL, "load module", do_load_module) || !add_callback(NULL, "unload module", do_unload_module) || !add_callback(NULL, "reconfigure", do_reconfigure) || !add_callback(NULL, "introduce_user", introduce_statserv) || !add_callback(NULL, "m_privmsg", statserv) || !add_callback(NULL, "m_whois", statserv_whois) || !add_callback(NULL, "server create", stats_do_server) || !add_callback(NULL, "server delete", stats_do_squit) || !add_callback(NULL, "user create", stats_do_newuser) || !add_callback(NULL, "user delete", stats_do_quit) || !add_callback(NULL, "user MODE", stats_do_umode) || !add_callback(NULL, "save data", do_save_data) ) { module_log("Unable to add callbacks"); exit_module(0); return 0; } tmpmod = find_module("nickserv/main"); if (tmpmod) do_load_module(tmpmod, "nickserv/main"); if (!open_statserv_db(StatDBName)) { module_log("Unable to read database"); exit_module(0); return 0; } db_opened = 1; if (linked) introduce_statserv(NULL); return 1; } /*************************************************************************/ int exit_module(int shutdown_unused) { #ifdef CLEAN_COMPILE shutdown_unused = shutdown_unused; #endif if (linked) send_cmd(s_StatServ, "QUIT :"); if (db_opened) close_statserv_db(StatDBName); if (module_nickserv) do_unload_module(module_nickserv); remove_callback(NULL, "save data", do_save_data); remove_callback(NULL, "user MODE", stats_do_umode); remove_callback(NULL, "user delete", stats_do_quit); remove_callback(NULL, "user create", stats_do_newuser); remove_callback(NULL, "server delete", stats_do_squit); remove_callback(NULL, "server create", stats_do_server); remove_callback(NULL, "m_whois", statserv_whois); remove_callback(NULL, "m_privmsg", statserv); remove_callback(NULL, "introduce_user", introduce_statserv); remove_callback(NULL, "reconfigure", do_reconfigure); remove_callback(NULL, "unload module", do_unload_module); remove_callback(NULL, "load module", do_load_module); unregister_callback(module, cb_help_cmds); unregister_callback(module, cb_help); unregister_callback(module, cb_command); unregister_commands(module, cmds); del_commandlist(module); return 1; } /*************************************************************************/