/* ChanServ internal utility routines. * * 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 STANDALONE_CHANSERV is defined when compiling this file, the * following routines will be defined as `static' for use in other * modules/programs: * new_channelinfo() * free_channelinfo() * This file should be #include'd in the file making use of the routines. */ #ifdef STANDALONE_CHANSERV # define STANDALONE_STATIC static # undef EXPORT_FUNC # define EXPORT_FUNC(x) /*nothing*/ #else # define STANDALONE_STATIC /*nothing*/ # include "services.h" # include "modules.h" # include "language.h" # include "modules/nickserv/nickserv.h" # include "chanserv.h" # include "cs-local.h" #endif /*************************************************************************/ #ifndef STANDALONE_CHANSERV static Module *module; #endif /*************************************************************************/ /************************ Global utility routines ************************/ /*************************************************************************/ /* Allocate and initialize a new ChannelInfo structure. */ EXPORT_FUNC(new_channelinfo) STANDALONE_STATIC ChannelInfo *new_channelinfo(void) { ChannelInfo *ci = scalloc(sizeof(ChannelInfo), 1); ci->memos.memomax = MEMOMAX_DEFAULT; return ci; } /*************************************************************************/ /* Free a ChannelInfo structure and all associated data. */ EXPORT_FUNC(free_channelinfo) STANDALONE_STATIC void free_channelinfo(ChannelInfo *ci) { int i; if (ci) { free(ci->desc); free(ci->url); free(ci->email); free(ci->last_topic); if (ci->suspendinfo) free_suspendinfo(ci->suspendinfo); free(ci->levels); free(ci->access); ARRAY_FOREACH (i, ci->akick) { free(ci->akick[i].mask); free(ci->akick[i].reason); } free(ci->akick); free(ci->mlock_key); free(ci->mlock_link); free(ci->mlock_flood); free(ci->entry_message); if (ci->memos.memos) { ARRAY_FOREACH (i, ci->memos.memos) free(ci->memos.memos[i].text); free(ci->memos.memos); } free(ci); } /* if (ci) */ } /*************************************************************************/ #ifndef STANDALONE_CHANSERV /* to the end of the file */ /*************************************************************************/ /* Check whether the given channel (or its suspension) should be expired, * and do the expiration if so. Return 1 if the channel was deleted, else 0. */ EXPORT_FUNC(check_expire_channel) int check_expire_channel(ChannelInfo *ci) { time_t now = time(NULL); Channel *c = ci->c; /* Don't bother updating last used time unless it's been a while */ if (c && CSExpire && now >= ci->last_used + CSExpire/2) { struct c_userlist *cu; LIST_FOREACH (cu, c->users) { if (check_access(cu->user, ci, CA_AUTOOP)) { if (debug >= 2) module_log("debug: updating last used time for %s", ci->name); ci->last_used = time(NULL); put_channelinfo(ci); break; } } } if (CSExpire && now >= ci->last_used + CSExpire && !(ci->flags & (CI_VERBOTEN | CI_NOEXPIRE)) && !ci->suspendinfo ) { module_log("Expiring channel %s", ci->name); if (chanmode_reg && c) { c->mode &= ~chanmode_reg; /* Send this out immediately, no set_cmode() delay */ send_cmode_cmd(s_ChanServ, ci->name, "-%s", mode_flags_to_string(chanmode_reg, MODE_CHANNEL)); } delchan(ci); return 1; } if (ci->suspendinfo && ci->suspendinfo->expires && now >= ci->suspendinfo->expires ) { module_log("Expiring suspension for %s", ci->name); unsuspend_channel(ci, 1); } return 0; } /*************************************************************************/ /* Check the nick group's number of registered channels against its limit, * and return -1 if below the limit, 0 if at it exactly, and 1 if over it. * If `max_ret' is non-NULL, store the nick group's registered channel * limit there. */ EXPORT_FUNC(check_channel_limit) int check_channel_limit(NickGroupInfo *ngi, int *max_ret) { register int max, count; max = ngi->channelmax; if (max == CHANMAX_DEFAULT) max = CSMaxReg; else if (max == CHANMAX_UNLIMITED) max = MAX_CHANNELCOUNT; count = ngi->channels_count; if (max_ret) *max_ret = max; return countname, chan, CHANMAX); ci->time_registered = time(NULL); reset_levels(ci, 0); return add_channelinfo(ci); } /*************************************************************************/ /* Remove a channel from the ChanServ database. Return 1 on success, 0 * otherwise. */ int delchan(ChannelInfo *ci) { User *u; Channel *c; /* Remove channel from founder's owned-channel count */ uncount_chan(ci); /* Clear link from channel record if it exists */ c = get_channel(ci->name); if (c) c->ci = NULL; /* Clear channel from users' identified-channel lists */ for (u = first_user(); u; u = next_user()) { struct u_chaninfolist *uc, *next; LIST_FOREACH_SAFE (uc, u->id_chans, next) { if (irc_stricmp(uc->chan, ci->name) == 0) { LIST_REMOVE(uc, u->id_chans); free(uc); } } } /* Now actually delete record */ del_channelinfo(ci); return 1; } /*************************************************************************/ /* Mark the given channel as owned by its founder. This updates the * founder's list of owned channels (ngi->channels). */ void count_chan(ChannelInfo *ci) { NickGroupInfo *ngi = ci->founder ? get_ngi_id(ci->founder) : NULL; if (!ngi) return; /* Be paranoid--this could overflow in extreme cases, though we check * for that elsewhere as well. */ if (ngi->channels_count >= MAX_CHANNELCOUNT) { module_log("count BUG: overflow in ngi->channels_count for %u (%s)" " on %s", ngi->id, ngi_mainnick(ngi), ci->name); return; } ARRAY_EXTEND(ngi->channels); strscpy(ngi->channels[ngi->channels_count-1], ci->name, CHANMAX); put_nickgroupinfo(ngi); } /*************************************************************************/ /* Mark the given channel as no longer owned by its founder. */ void uncount_chan(ChannelInfo *ci) { NickGroupInfo *ngi = ci->founder ? get_ngi_id(ci->founder) : NULL; int i; if (!ngi) return; ARRAY_SEARCH_PLAIN(ngi->channels, ci->name, irc_stricmp, i); if (i >= ngi->channels_count) { module_log("uncount BUG: channel not found in channels[] for %u (%s)" " on %s", ngi->id, ngi->nicks_count ? ngi_mainnick(ngi) : "", ci->name); return; } ARRAY_REMOVE(ngi->channels, i); put_nickgroupinfo(ngi); } /*************************************************************************/ /* Does the given user have founder access to the channel? */ int is_founder(User *user, ChannelInfo *ci) { if (!ci || (ci->flags&CI_VERBOTEN) || ci->suspendinfo || !valid_ngi(user)) return 0; if (user->ngi->id == ci->founder) { if ((user_identified(user) || (user_recognized(user) && !(ci->flags & CI_SECURE)))) return 1; } if (is_identified(user, ci)) return 1; return 0; } /*************************************************************************/ /* Has the given user password-identified as founder for the channel? */ int is_identified(User *user, ChannelInfo *ci) { struct u_chaninfolist *c; if (!ci) return 0; LIST_FOREACH (c, user->id_chans) { if (irc_stricmp(c->chan, ci->name) == 0) return 1; } return 0; } /*************************************************************************/ /* Restore the topic in a channel when it's created, if we should. */ void restore_topic(Channel *c) { ChannelInfo *ci = c->ci; if (ci && (ci->flags & CI_KEEPTOPIC) && ci->last_topic && *ci->last_topic){ set_topic(s_ChanServ, c, ci->last_topic, *ci->last_topic_setter ? ci->last_topic_setter : s_ChanServ, ci->last_topic_time); } } /*************************************************************************/ /* Record a channel's topic in its ChannelInfo structure. */ void record_topic(ChannelInfo *ci, const char *topic, const char *setter, time_t topic_time) { if (readonly || !ci) return; free(ci->last_topic); if (*topic) ci->last_topic = sstrdup(topic); else ci->last_topic = NULL; strscpy(ci->last_topic_setter, setter, NICKMAX); ci->last_topic_time = topic_time; put_channelinfo(ci); } /*************************************************************************/ /* Create a new SuspendInfo structure and associate it with the given * channel. */ void suspend_channel(ChannelInfo *ci, const char *reason, const char *who, const time_t expires) { SuspendInfo *si = new_suspendinfo(who, sstrdup(reason), expires); ci->suspendinfo = si; put_channelinfo(ci); } /*************************************************************************/ /* Delete the suspension data for the given channel. We also alter the * last_seen value to ensure that it does not expire within the next * CSSuspendGrace seconds, giving its users a chance to reclaim it * (but only if set_time is non-zero). */ void unsuspend_channel(ChannelInfo *ci, int set_time) { time_t now = time(NULL); if (!ci->suspendinfo) { module_log("unsuspend_channel() called on non-suspended channel %s", ci->name); return; } free_suspendinfo(ci->suspendinfo); ci->suspendinfo = NULL; if (set_time && CSExpire && CSSuspendGrace && (now - ci->last_used >= CSExpire - CSSuspendGrace) ) { ci->last_used = now - CSExpire + CSSuspendGrace; module_log("unsuspend: Altering last_used time for %s to %ld", ci->name, (long)ci->last_used); } put_channelinfo(ci); } /*************************************************************************/ /* Register a bad password attempt for a channel. */ void chan_bad_password(User *u, ChannelInfo *ci) { bad_password(s_ChanServ, u, ci->name); ci->bad_passwords++; if (BadPassWarning && ci->bad_passwords == BadPassWarning) { wallops(s_ChanServ, "\2Warning:\2 Repeated bad password attempts" " for channel %s (last attempt by user %s)", ci->name, u->nick); } } /*************************************************************************/ /* Return the ChanOpt structure for the given option name. If not found, * return NULL. */ ChanOpt *chanopt_from_name(const char *optname) { int i; for (i = 0; chanopts[i].name; i++) { if (stricmp(chanopts[i].name, optname) == 0) return &chanopts[i]; } return NULL; } /*************************************************************************/ /* Return a string listing the options (those given in chanopts[]) set on * the given channel. Uses the given NickGroupInfo for language * information. The returned string is stored in a static buffer which * will be overwritten on the next call. */ char *chanopts_to_string(ChannelInfo *ci, NickGroupInfo *ngi) { static char buf[BUFSIZE]; char *end = buf; const char *commastr = getstring(ngi, COMMA_SPACE); const char *s; int need_comma = 0; int i; for (i = 0; chanopts[i].name && end-buf < sizeof(buf)-1; i++) { if (chanopts[i].namestr < 0) continue; if (ci->flags & chanopts[i].flag) { s = getstring(ngi, chanopts[i].namestr); if (!s) continue; if (need_comma) end += snprintf(end, sizeof(buf)-(end-buf), "%s", commastr); end += snprintf(end, sizeof(buf)-(end-buf), "%s", s); need_comma = 1; } } return buf; } /*************************************************************************/ /*************************************************************************/ int init_util(Module *my_module) { module = my_module; return 1; } /*************************************************************************/ void exit_util() { } /*************************************************************************/ #endif /* !STANDALONE_CHANSERV */