/* SJOIN-related functions. * * 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. */ /* If this file is compiled with UNREAL_HACK defined, the create-channel * callback, do_channel_create(), will be modified to force Unreal to * update channel times properly. Note that this will cause the exported * functions (do_sjoin, init_sjoin, and exit_sjoin) to have "_unreal" * appended to their names; sjoin.h performs the renaming using #defines * if UNREAL_HACK is defined. * * Defining BAHAMUT_HACK does the same thing for Bahamut. */ #include "services.h" #include "modules.h" #include "modules/chanserv/chanserv.h" #include "sjoin.h" /*************************************************************************/ static Module *module; static Module *module_database; int CSSetChannelTime; /*************************************************************************/ /* Handle an SJOIN command. * Bahamut no-SSJOIN format: * av[0] = TS3 timestamp * av[1] = TS3 timestamp - channel creation time * av[2] = channel * av[3] = channel modes * av[4] = limit / key (depends on modes in av[3]) * av[5] = limit / key (depends on modes in av[3]) * av[ac-1] = nickname(s), with modes, joining channel * Bahamut SSJOIN (server source) / Hybrid / PTlink / Unreal SJOIN2/SJ3 format: * av[0] = TS3 timestamp - channel creation time * av[1] = channel * av[2] = modes (limit/key in av[3]/av[4] as needed) * av[ac-1] = users * (Note that Unreal may omit the modes if there aren't any. * Unreal SJ3 also includes bans and exceptions in av[ac-1] * with prefix characters of & and " respectively.) * Bahamut SSJOIN format (client source): * av[0] = TS3 timestamp - channel creation time * av[1] = channel */ void do_sjoin(const char *source, int ac, char **av) { User *user; char *t, *nick; char *channel; Channel *c = NULL, *ctemp; if (isdigit(av[1][0])) { /* Plain SJOIN format, zap join timestamp */ memmove(&av[0], &av[1], sizeof(char *) * (ac-1)); ac--; } channel = av[1]; if (ac >= 3) { /* SJOIN from server: nick list in last param */ t = av[ac-1]; } else { /* SJOIN for a single client: source is nick */ /* We assume the nick has no spaces, so we can discard const */ if (strchr(source, ' ')) fatal("sjoin: source nick contains spaces!"); t = (char *)source; } while (*(nick=t)) { int32 modes = 0, thismode; t = nick + strcspn(nick, " "); if (*t) *t++ = 0; if (*nick == '&' || *nick == '"') { /* Ban (&) or exception (") */ char *av[3]; av[0] = channel; av[1] = (char *)((*nick=='&') ? "+b" : "+e"); av[2] = nick+1; do_cmode(source, 3, av); continue; } do { thismode = cumode_prefix_to_flag(*nick); modes |= thismode; } while (thismode && *nick++); /* increment nick only if thismode!=0 */ user = get_user(nick); if (!user) { module_log("sjoin: SJOIN to channel %s for non-existent nick %s" " (%s)", channel, nick, merge_args(ac-1, av)); continue; } if (debug) module_log("debug: %s SJOINs %s", nick, channel); if ((ctemp = join_channel(user, channel, modes)) != NULL) c = ctemp; } /* Did anyone actually join the channel? */ if (c) { /* Store channel timestamp in channel structure, unless we set it * ourselves. */ if (!c->ci) c->creation_time = strtotime(av[0], NULL); /* Set channel modes if there are any. Note how argument list is * conveniently set up for do_cmode(). */ if (ac > 3) do_cmode(source, ac-2, av+1); } } /*************************************************************************/ /* Clear out all channel modes using an SJOIN (for CLEAR_USERS). */ static int sjoin_clear_users(const char *sender, Channel *chan, int what, const void *param) { if (what & CLEAR_USERS) { int i; send_cmd(ServerName, "SJOIN %ld %s + :", (long)(chan->creation_time - 1), chan->name); ARRAY_FOREACH (i, chan->excepts) free(chan->excepts[i]); chan->excepts_count = 0; } return 0; } /*************************************************************************/ /*************************************************************************/ /* Callback to set the creation time for a registered channel to the * channel's registration time. This callback is added before the main * ChanServ module is loaded, so c->ci will not yet be set. */ static typeof(get_channelinfo) *p_get_channelinfo = NULL; static int do_channel_create(Channel *c, User *u, int32 modes) { if (CSSetChannelTime && p_get_channelinfo) { ChannelInfo *ci = p_get_channelinfo(c->name); if (ci) { c->creation_time = ci->time_registered; #ifdef UNREAL_HACK /* NOTE: this is a bit of a kludge, since Unreal's SJOIN * doesn't let us set just the channel creation time while * leaving the modes alone. */ send_cmd(ServerName, "SJOIN %ld %s %co %s :", (long)c->creation_time, c->name, (modes & CUMODE_o ? '+' : '-'), u->nick); #else send_cmd(ServerName, "SJOIN %ld %s + :%s%s", (long)c->creation_time, c->name, (modes & CUMODE_o ? "@" : ""), u->nick); #endif #ifdef BAHAMUT_HACK if (modes & CUMODE_o) { /* Bahamut ignores users in the user list which aren't on * or behind the server sending the SJOIN, so we need an * extra MODE to explicitly give ops back to the initial * joining user. */ send_cmode_cmd(ServerName, c->name, "+o :%s", u->nick); } #endif } } return 0; } /*************************************************************************/ /* Callback to watch for modules being loaded. */ static int do_load_module(Module *mod, const char *name) { if (strncmp(name, "database/", 9) == 0) { module_database = mod; p_get_channelinfo = get_module_symbol(NULL, "get_channelinfo"); if (!p_get_channelinfo) module_log("sjoin: unable to resolve symbol `get_channelinfo' in" " database module, channel time setting disabled"); } return 0; } /*************************************************************************/ /* Callback to watch for modules being unloaded. */ static int do_unload_module(Module *mod) { if (mod == module_database) { p_get_channelinfo = NULL; module_database = NULL; } return 0; } /*************************************************************************/ /* Initialization. */ int init_sjoin(Module *module_) { module = module_; if (!add_callback(NULL, "load module", do_load_module) || !add_callback(NULL, "unload module", do_unload_module) || !add_callback(NULL, "channel create", do_channel_create) || !add_callback(NULL, "clear channel", sjoin_clear_users) ) { module_log("sjoin: Unable to add callbacks"); exit_sjoin(); return 0; } return 1; } /*************************************************************************/ /* Cleanup. */ void exit_sjoin(void) { remove_callback(NULL, "clear channel", sjoin_clear_users); remove_callback(NULL, "channel create", do_channel_create); remove_callback(NULL, "unload module", do_unload_module); remove_callback(NULL, "load module", do_load_module); } /*************************************************************************/