/* ChanServ internal utility routines.
*
* 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.
*/
/* 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 count<max ? -1 : count==max ? 0 : 1;
}
/*************************************************************************/
/*********************** Internal utility routines ***********************/
/*************************************************************************/
/* Add a channel to the database. Returns a pointer to the new ChannelInfo
* structure if the channel was successfully registered, NULL otherwise.
* Assumes channel does not already exist. */
ChannelInfo *makechan(const char *chan)
{
ChannelInfo *ci;
ci = scalloc(sizeof(ChannelInfo), 1);
strscpy(ci->name, 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) : "<unknown>",
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 */
syntax highlighted by Code2HTML, v. 0.9.1