/* Routines to load/save Services databases in the (hopefully one day
* obsolete) format used by version 4.x.
*
* 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.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "encrypt.h"
#include "hash.h"
#include "language.h"
#define STANDALONE_NICKSERV
#define STANDALONE_CHANSERV
#include "modules/nickserv/nickserv.h"
#include "modules/chanserv/chanserv.h"
#include "modules/memoserv/memoserv.h"
#include "modules/operserv/operserv.h"
#include "modules/operserv/maskdata.h"
#include "modules/operserv/akill.h"
#include "modules/operserv/news.h"
#include "modules/operserv/sessions.h"
#include "modules/operserv/sline.h"
#include "modules/statserv/statserv.h"
/* New/free functions */
#include "modules/nickserv/util.c"
#include "modules/chanserv/util.c"
#include "extsyms.h"
#include "fileutil.h"
#define SAFE(x) do { if ((x) < 0) goto fail; } while (0)
/*************************************************************************/
static Module *module;
static Module *module_operserv;
static Module *module_operserv_akill;
static Module *module_operserv_news;
static Module *module_operserv_sline;
static Module *module_nickserv;
static Module *module_chanserv;
static Module *module_statserv;
/*************************************************************************/
#define FILE_VERSION 11 /* Must remain constant */
#define LOCAL_VERSION 27 /* For extensions to database files */
/* LOCAL_VERSION change history:
* 27: Added Bahamut +j handling (ci->mlock_joinrate{1,2} fields)
* 26: Forced AUTODEOP and NOJOIN to default values
* 25: Added trircd +J handling (ci->mlock_joindelay field)
* 24: Moved nickname authentication reason into its own field (no
* longer stored as part of authentication code)
* 23: Added count to autokick entries in channel extension data
* 22: Fixed bug causing nickgroups with ID 0 to get written out
* 21: AUTODEOP and NOJOIN levels changed from -10/-20 to -1/-100
* 20: Access levels changed; v5 level data and access entry levels
* added to channel extension data
* 19: Added last IDENTIFY stamp to nickname extension data
* 18: Added autojoin functionality; added autojoin list to nickgroup
* extension data
* 17: Added memo ignore functionality; added ignore list to nickgroup
* extension data
* 16: Added Unreal +L/+f handling; added respective fields to channel
* extension data
* 15: Added nick timezones; added timezone field to nickgroup extension
* data
* 14: Added autokick time and lastused fields (saved to channel
* extension data)
* 13: Added nickname privilege level to nickgroup extension data
*/
/* Default channel entries in version 4.5: */
#define CA_SIZE_4_5 18
#define ACCLEV_INVALID_4_5 -10000
static int def_levels_4_5[CA_SIZE_4_5] = {
/* CA_INVITE */ 5,
/* CA_AKICK */ 10,
/* CA_SET */ ACCLEV_INVALID_4_5,
/* CA_UNBAN */ 5,
/* CA_AUTOOP */ 5,
/* CA_AUTODEOP */ -1,
/* CA_AUTOVOICE */ 3,
/* CA_OPDEOP */ 5,
/* CA_ACCESS_LIST */ 0,
/* CA_CLEAR */ ACCLEV_INVALID_4_5,
/* CA_NOJOIN */ -2,
/* CA_ACCESS_CHANGE */ 10,
/* CA_MEMO */ 10,
/* CA_VOICE */ 3,
/* CA_AUTOHALFOP */ 4,
/* CA_HALFOP */ 4,
/* CA_AUTOPROTECT */ 10,
/* CA_PROTECT */ 10,
};
/* For recording Services opers/admins for oper.db: */
#define MAX_SERVOPERS 256
#define MAX_SERVADMINS 256
static nickname_t *services_admins = NULL, *services_opers = NULL;
static int services_admins_count = 0, services_opers_count = 0;
/*************************************************************************/
/* Common routine to open a file for reading and check version number. */
#define OPENDB_ERROR ((dbFILE *)PTR_INVALID)
static dbFILE *my_open_db_r(const char *dbname, int32 *ver_ret)
{
dbFILE *f;
int32 ver;
f = open_db(dbname, "r", 0);
if (!f)
return NULL;
ver = get_file_version(f);
if (ver < 5 || ver > 11) {
if (ver == -1) {
module_log("Unable to read version number from %s",
dbname);
} else {
module_log("Invalid version number %d on %s", ver,
dbname);
}
close_db(f);
return OPENDB_ERROR;
}
*ver_ret = ver;
return f;
}
/*************************************************************************/
/********************** NickServ database handling ***********************/
/*************************************************************************/
#undef EXPIRE_CHECK
#define EXPIRE_CHECK(node) check_expire_nick(node)
#define add_nickinfo static _add_nickinfo
#define del_nickinfo static _del_nickinfo
DEFINE_HASH(nickinfo, NickInfo, nick)
#undef add_nickinfo
#undef del_nickinfo
NickInfo *add_nickinfo(NickInfo *ni)
{
_add_nickinfo(ni);
return ni;
}
void del_nickinfo(NickInfo *ni)
{
_del_nickinfo(ni);
free_nickinfo(ni);
}
void put_nickinfo(NickInfo *ni) {}
/*************************************************************************/
#undef HASHFUNC
#define HASHFUNC(key) (((uint32)(key)*31) % HASHSIZE)
#undef EXPIRE_CHECK
#define EXPIRE_CHECK(node) 0
#define add_nickgroupinfo static _add_nickgroupinfo
#define del_nickgroupinfo static _del_nickgroupinfo
DEFINE_HASH_SCALAR(nickgroupinfo, NickGroupInfo, id, uint32);
#undef add_nickgroupinfo
#undef del_nickgroupinfo
static int next_id; /* for loading */
NickGroupInfo *add_nickgroupinfo(NickGroupInfo *ngi)
{
_add_nickgroupinfo(ngi);
return ngi;
}
void del_nickgroupinfo(NickGroupInfo *ngi)
{
_del_nickgroupinfo(ngi);
free_nickgroupinfo(ngi);
}
void put_nickgroupinfo(NickGroupInfo *ngi) {}
/*************************************************************************/
static NickInfo *load_one_nick(dbFILE *f, int32 ver)
{
NickInfo *ni;
NickGroupInfo *ngi;
int16 tmp16;
int32 tmp32;
int i;
char passbuf[PASSMAX];
char *url, *email;
ni = new_nickinfo();
SAFE(read_buffer(ni->nick, f));
if (debug >= 2)
module_log("debug: loading nick %s", ni->nick);
SAFE(read_buffer(passbuf, f));
SAFE(read_string(&url, f));
SAFE(read_string(&email, f));
SAFE(read_string(&ni->last_usermask, f));
if (!ni->last_usermask)
ni->last_usermask = sstrdup("@");
SAFE(read_string(&ni->last_realname, f));
if (!ni->last_realname)
ni->last_realname = sstrdup("");
SAFE(read_string(&ni->last_quit, f));
SAFE(read_int32(&tmp32, f));
ni->time_registered = tmp32;
SAFE(read_int32(&tmp32, f));
ni->last_seen = tmp32;
SAFE(read_int16(&ni->status, f));
ni->status &= ~NS_TEMPORARY | 0x0001; /* 0x0001 was ENCRYPTEDPW */
/* Old-style links were hierarchical; if this nick was linked to
* another, the name of the parent link, else NULL, was stored here.
* Store that value in ni->last_realmask, which coincidentally was
* not saved in old versions, and resolve links later. */
SAFE(read_string(&ni->last_realmask, f));
SAFE(read_int16(&tmp16, f)); /* linkcount */
if (ni->last_realmask) {
SAFE(read_int16(&tmp16, f)); /* channelcount */
free(url);
free(email);
} else {
ngi = new_nickgroupinfo(NULL);
ngi->id = next_id++;
ARRAY_EXTEND(ngi->nicks);
strscpy(ngi->nicks[0], ni->nick, NICKMAX);
memcpy(ngi->pass, passbuf, PASSMAX);
ngi->url = url;
ngi->email = email;
SAFE(read_int32(&ngi->flags, f));
if (ngi->flags & NF_KILL_IMMED)
ngi->flags |= NF_KILL_QUICK;
if (ver >= 9) {
read_ptr((void **)&ngi->suspendinfo, f);
} else if (ver == 8 && (ngi->flags & 0x10000000)) {
/* In version 8, 0x10000000 was NI_SUSPENDED */
ngi->suspendinfo = (SuspendInfo *)1;
}
if (ngi->suspendinfo) {
SuspendInfo *si = smalloc(sizeof(*si));
SAFE(read_buffer(si->who, f));
SAFE(read_string(&si->reason, f));
SAFE(read_int32(&tmp32, f));
si->suspended = tmp32;
SAFE(read_int32(&tmp32, f));
si->expires = tmp32;
ngi->suspendinfo = si;
}
SAFE(read_int16(&ngi->access_count, f));
if (ngi->access_count) {
char **access;
access = smalloc(sizeof(char *) * ngi->access_count);
ngi->access = access;
ARRAY_FOREACH (i, ngi->access)
SAFE(read_string(&ngi->access[i], f));
}
SAFE(read_int16(&ngi->memos.memos_count, f));
SAFE(read_int16(&ngi->memos.memomax, f));
/*
* Note that at this stage we have no way of comparing this to the
* default memo max (MSMaxMemos) because the MemoServ module is not
* loaded. If this is a 5.x database, this is not a problem,
* because the correct memo max value will be stored in the
* extension data, but if not, we need to check and change the
* value to MEMOMAX_DEFAULT as needed. This is handled by a
* callback (nick_memomax_callback() below) which triggers when the
* MemoServ module is loaded. The callback is added by
* open_nick_db() if needed.
*/
if (ngi->memos.memos_count) {
Memo *memos;
memos = smalloc(sizeof(Memo) * ngi->memos.memos_count);
ngi->memos.memos = memos;
ARRAY_FOREACH (i, ngi->memos.memos) {
SAFE(read_uint32(&ngi->memos.memos[i].number, f));
SAFE(read_int16(&ngi->memos.memos[i].flags, f));
SAFE(read_int32(&tmp32, f));
ngi->memos.memos[i].time = tmp32;
SAFE(read_buffer(ngi->memos.memos[i].sender, f));
SAFE(read_string(&ngi->memos.memos[i].text, f));
}
}
/* Channel counts are recalculated by open_channel_db() */
SAFE(read_int16(&tmp16, f)); /* channelcount */
/* If this is a 5.x database, we now get the real nickgroup value
* from bits 30-15 of the flags and the 16 bits we just read; the
* real flags value is stored in the extension data. */
if (ngi->flags & 0x80000000)
ngi->id = (ngi->flags & 0x7FFF8000) << 1 | ((int)tmp16 & 0xFFFF);
/* There was no way to set channel limits, so must be the default.
* Note that if this is a 5.x database, the correct value for this
* field (as well as memomax and language) will be read from the
* extension data. */
SAFE(read_int16(&tmp16, f)); /* channelmax */
ngi->channelmax = CHANMAX_DEFAULT;
SAFE(read_int16(&ngi->language, f));
if (!have_language(ngi->language))
ngi->language = LANG_DEFAULT;
/* Ver 4.x had no way to say "use the default language", so set that
* for all nicks that are using DEF_LANGUAGE */
if (ngi->language == DEF_LANGUAGE)
ngi->language = LANG_DEFAULT;
ngi->timezone = TIMEZONE_DEFAULT;
ni->nickgroup = ngi->id;
if (ngi->id != 0) {
add_nickgroupinfo(ngi);
} else {
free_nickgroupinfo(ngi);
if (!(ni->status & NS_VERBOTEN)) {
module_log("warning: nick %s has no nick group but is not"
" forbidden (corrupt database or BUG?)", ni->nick);
}
}
}
return ni;
fail:
module_log("Read error on %s", f->filename);
return NULL;
}
/*************************************************************************/
/* Load extension data for a nick. Returns zero on success, nonzero on
* failure.
*/
static int load_one_nick_ext(dbFILE *f, int32 ver)
{
char *nick;
NickInfo *ni;
NickInfo dummy_ni; /* for nonexisting nicks */
SAFE(read_string(&nick, f));
if (!nick)
goto fail;
if (debug >= 2)
module_log("debug: loading nick extension %s", nick);
if (!(ni = get_nickinfo(nick))) {
module_log("Extension data found for nonexisting nick `%s'", nick);
ni = &dummy_ni;
memset(ni, 0, sizeof(*ni));
}
free(nick);
nick = NULL;
free(ni->last_realmask); /* copied from last_usermask */
SAFE(read_string(&ni->last_realmask, f));
if (ver >= 19)
SAFE(read_uint32(&ni->id_stamp, f));
if (ni == &dummy_ni)
free(ni->last_realmask);
return 0;
fail:
module_log("Read error on %s", f->filename);
return 1;
}
/*************************************************************************/
/* Load extension data for a nick group. */
static int load_one_nickgroup_ext(dbFILE *f, int32 ver)
{
uint32 group;
NickGroupInfo *ngi;
NickGroupInfo dummy_ngi; /* for nonexisting nick groups */
int i;
SAFE(read_uint32(&group, f));
if (debug >= 2)
module_log("debug: loading nickgroup extension %u", group);
if (!group) {
if (ver < 22) {
module_log("Ignoring nickgroup 0 (bug in previous versions)");
ngi = &dummy_ngi;
memset(ngi, 0, sizeof(*ngi));
} else {
goto fail;
}
} else if (!(ngi = get_nickgroupinfo(group))) {
module_log("Extension data found for nonexisting nick group %u",
group);
ngi = &dummy_ngi;
memset(ngi, 0, sizeof(*ngi));
}
SAFE(read_int32(&ngi->flags, f));
SAFE(read_int32(&ngi->authcode, f));
SAFE(read_time(&ngi->authset, f));
if (ver >= 24) {
SAFE(read_int16(&ngi->authreason, f));
} else {
switch ((ngi->authcode & 0x300) >> 8) {
case 0 : ngi->authreason = NICKAUTH_REGISTER; break;
case 1 : ngi->authreason = NICKAUTH_SET_EMAIL; break;
case 2 : ngi->authreason = NICKAUTH_SETAUTH; break;
default: ngi->authreason = 0; break;
}
}
SAFE(read_int16(&ngi->channelmax, f));
if (ver >= 18) {
SAFE(read_int16(&ngi->ajoin_count, f));
if (ngi->ajoin_count) {
ngi->ajoin = smalloc(sizeof(char *) * ngi->ajoin_count);
ARRAY_FOREACH (i, ngi->ajoin)
SAFE(read_string(&ngi->ajoin[i], f));
}
}
SAFE(read_int16(&ngi->memos.memomax, f));
if (ver >= 17) {
SAFE(read_int16(&ngi->ignore_count, f));
if (ngi->ignore_count) {
ngi->ignore = smalloc(sizeof(char *) * ngi->ignore_count);
ARRAY_FOREACH (i, ngi->ignore)
SAFE(read_string(&ngi->ignore[i], f));
}
}
SAFE(read_int16(&ngi->language, f));
if (!have_language(ngi->language))
ngi->language = LANG_DEFAULT;
if (ver >= 15)
SAFE(read_int16(&ngi->timezone, f));
SAFE(read_string(&ngi->info, f));
if (ver >= 13)
SAFE(read_int16(&ngi->os_priv, f));
if (ngi == &dummy_ngi) {
ARRAY_FOREACH (i, ngi->ajoin)
free(ngi->ajoin[i]);
free(ngi->ajoin);
ARRAY_FOREACH (i, ngi->ignore)
free(ngi->ignore[i]);
free(ngi->ignore);
}
return 0;
fail:
module_log("Read error on %s", f->filename);
return 1;
}
/*************************************************************************/
static int nick_memomax_callback(Module *mod, const char *name)
{
NickGroupInfo *ngi;
#ifdef CLEAN_COMPILE
mod = mod;
#endif
if (strcmp(name, "memoserv/main") != 0)
return 0;
for (ngi = first_nickgroupinfo(); ngi; ngi = next_nickgroupinfo()) {
if (ngi->memos.memomax == MSMaxMemos)
ngi->memos.memomax = MEMOMAX_DEFAULT;
}
/* We only need to do this once */
remove_callback(NULL, "load module", nick_memomax_callback);
return 0;
}
/*************************************************************************/
int open_nick_db(const char *dbname)
{
dbFILE *f;
int32 ver;
int i, c;
NickInfo *ni;
int failed = 0;
int need_memomax_check = 1;
/* Open database. */
if (!(f = my_open_db_r(dbname, &ver)))
return 1;
else if (f == OPENDB_ERROR)
return 0;
/* Load original data. */
next_id = 1;
for (i = 0; i < 256 && !failed; i++) {
while ((c = getc_db(f)) != 0) {
if (c != 1)
fatal("database/version4: Invalid format in %s", dbname);
ni = load_one_nick(f, ver);
if (ni) {
add_nickinfo(ni);
} else {
failed = 1;
break;
}
}
}
/* Resolve links. First point each last_realmask field at the
* NickInfo * of the appropriate nick; then copy the nickgroup ID from
* each root nick to all of its children, effectively collapsing the
* link hierarchies to a single level, and add the child nicks to the
* root nickgroup's nick array.
*/
for (ni = first_nickinfo(); ni; ni = next_nickinfo()) {
if (ni->last_realmask) {
char *s = ni->last_realmask;
ni->last_realmask = (char *)get_nickinfo(s);
free(s);
}
}
for (ni = first_nickinfo(); ni; ni = next_nickinfo()) {
if (ni->last_realmask) {
NickInfo *root = ni;
NickGroupInfo *ngi;
do {
root = (NickInfo *)root->last_realmask;
} while (root->last_realmask);
ni->nickgroup = root->nickgroup;
ngi = get_nickgroupinfo(ni->nickgroup);
if (!ngi) {
module_log("BUG: Unable to find nickgroup %u for linked"
" nick %s (parent = %s, root = %s)",
ni->nickgroup, ni->nick,
((NickInfo *)ni->last_realmask)->nick,
root->nick);
} else {
ARRAY_EXTEND(ngi->nicks);
strscpy(ngi->nicks[ngi->nicks_count-1], ni->nick, NICKMAX);
}
}
if (!ni->nickgroup && !(ni->status & NS_VERBOTEN)) {
module_log("Nick %s has no settings (linked to missing nick?),"
" deleting", ni->nick);
ni->last_realmask = NULL; /* Don't free someone else's NickInfo */
del_nickinfo(ni);
}
}
/* Copy all last_usermask fields to last_realmask. */
for (ni = first_nickinfo(); ni; ni = next_nickinfo()) {
ni->last_realmask = sstrdup(ni->last_usermask);
}
/* Load extension data if present. */
ver = 0;
if (!failed && read_int32(&ver, f) == 0) {
if (ver <= FILE_VERSION || ver > LOCAL_VERSION)
fatal("database/version4: Invalid extension data version in %s",
dbname);
while ((c = getc_db(f)) != 0) {
if (c != 1)
fatal("database/version4: Invalid format in %s extension"
" data", dbname);
if ((failed = load_one_nick_ext(f, ver)) != 0)
break;
}
while ((c = getc_db(f)) != 0) {
if (c != 1)
fatal("database/version4: Invalid format in %s extension"
" data", dbname);
if ((failed = load_one_nickgroup_ext(f, ver)) != 0)
break;
}
need_memomax_check = 0;
}
if (ver < 13) {
/* Need to restore Services admin/oper privs from oper.db lists */
NickGroupInfo *ngi;
ARRAY_FOREACH (i, services_admins) {
ni = get_nickinfo(services_admins[i]);
if (ni != NULL && (ngi = get_ngi(ni)) != NULL)
ngi->os_priv = NP_SERVADMIN;
}
ARRAY_FOREACH (i, services_opers) {
ni = get_nickinfo(services_opers[i]);
if (ni != NULL && (ngi = get_ngi(ni)) != NULL)
ngi->os_priv = NP_SERVOPER;
}
}
/* Add the memomax check callback if needed. */
if (need_memomax_check)
add_callback(NULL, "load module", nick_memomax_callback);
/* Close database. */
close_db(f);
/* All done! */
return !failed || forceload;
}
/*************************************************************************/
/* NickGroupInfo for use with forbidden nicks; initialized to all zeroes */
static NickGroupInfo forbidden_ngi;
int sync_nick_db(const char *dbname)
{
dbFILE *f;
int i;
NickInfo *ni;
NickGroupInfo *ngi;
static time_t lastwarn = 0;
if (!(f = open_db(dbname, "w", 11)))
return 0;
for (ni = first_nickinfo(); ni; ni = next_nickinfo()) {
if (ni->nickgroup)
ngi = get_nickgroupinfo(ni->nickgroup);
else
ngi = NULL;
SAFE(write_int8(1, f));
SAFE(write_buffer(ni->nick, f));
if (ngi) {
SAFE(write_buffer(ngi->pass, f));
SAFE(write_string(ngi->url, f));
SAFE(write_string(ngi->email, f));
} else {
char dummypass[PASSMAX];
if (!(ni->status & NS_VERBOTEN)) {
module_log("nick %s has no NickGroupInfo, setting password"
" to nick", ni->nick);
encrypt(ni->nick, strlen(ni->nick), dummypass, PASSMAX);
} else {
memset(dummypass, 0, sizeof(dummypass));
}
SAFE(write_buffer(dummypass, f));
memset(dummypass, 0, sizeof(dummypass));
SAFE(write_string(NULL, f));
SAFE(write_string(NULL, f));
}
SAFE(write_string(ni->last_usermask, f));
SAFE(write_string(ni->last_realname, f));
SAFE(write_string(ni->last_quit, f));
SAFE(write_int32(ni->time_registered, f));
SAFE(write_int32(ni->last_seen, f));
SAFE(write_int16(ni->status, f));
if (ngi && irc_stricmp(ni->nick, ngi_mainnick(ngi)) != 0) {
/* Not the main nick in the group; save it as a link */
SAFE(write_string(ngi_mainnick(ngi), f));
SAFE(write_int16(0, f));
SAFE(write_int16(0, f));
} else {
int32 tmp32;
/* Main nick in the group, or forbidden; save as a root nick */
SAFE(write_string(NULL, f));
SAFE(write_int16(0, f));
/* If it's forbidden, use a dummy NickGroupInfo from here on */
if (!ngi)
ngi = &forbidden_ngi;
/* Store top 16 bits of group ID in flags */
tmp32 = ngi->flags | 0x80000000 | (ngi->id & 0xFFFF0000)>>1;
if (tmp32 & NF_KILL_IMMED)
tmp32 &= ~NF_KILL_QUICK;
SAFE(write_int32(tmp32, f));
SAFE(write_ptr(ngi->suspendinfo, f));
if (ngi->suspendinfo) {
SAFE(write_buffer(ngi->suspendinfo->who, f));
SAFE(write_string(ngi->suspendinfo->reason, f));
SAFE(write_int32(ngi->suspendinfo->suspended, f));
SAFE(write_int32(ngi->suspendinfo->expires, f));
}
SAFE(write_int16(ngi->access_count, f));
ARRAY_FOREACH (i, ngi->access)
SAFE(write_string(ngi->access[i], f));
SAFE(write_int16(ngi->memos.memos_count, f));
/* Note that we have to save the memo maximum here as a static
* value; we save the real value (which may be MEMOMAX_DEFAULT)
* in the extension area below. The same applies for channelmax
* and language. */
if (ngi->memos.memomax == MEMOMAX_DEFAULT)
SAFE(write_int16(MSMaxMemos, f));
else
SAFE(write_int16(ngi->memos.memomax, f));
ARRAY_FOREACH (i, ngi->memos.memos) {
SAFE(write_int32(ngi->memos.memos[i].number, f));
SAFE(write_int16(ngi->memos.memos[i].flags, f));
SAFE(write_int32(ngi->memos.memos[i].time, f));
SAFE(write_buffer(ngi->memos.memos[i].sender, f));
SAFE(write_string(ngi->memos.memos[i].text, f));
}
/* Store bottom 16 bits of group ID in channelcount */
SAFE(write_int16(ngi->id & 0xFFFF, f));
if (ngi->channelmax == CHANMAX_DEFAULT)
SAFE(write_int16(CSMaxReg, f));
else
SAFE(write_int16(ngi->channelmax, f));
if (ngi->language == LANG_DEFAULT)
SAFE(write_int16(DEF_LANGUAGE, f));
else
SAFE(write_int16(ngi->language, f));
}
} /* for (ni) */
{
/* This is an UGLY HACK but it simplifies loading. */
static char buf[256];
SAFE(write_buffer(buf, f));
}
free(services_admins);
free(services_opers);
services_admins = services_opers = NULL;
services_admins_count = services_opers_count = 0;
SAFE(write_int32(LOCAL_VERSION, f));
for (ni = first_nickinfo(); ni; ni = next_nickinfo()) {
SAFE(write_int8(1, f));
SAFE(write_string(ni->nick, f));
SAFE(write_string(ni->last_realmask, f));
SAFE(write_int32(ni->id_stamp, f));
}
SAFE(write_int8(0, f));
for (ngi = first_nickgroupinfo(); ngi; ngi = next_nickgroupinfo()) {
if (ngi->id == 0) {
module_log("BUG: nickgroup with ID 0 found during write"
" (skipping)");
continue;
}
SAFE(write_int8(1, f));
SAFE(write_int32(ngi->id, f));
SAFE(write_int32(ngi->flags, f));
SAFE(write_int32(ngi->authcode, f));
SAFE(write_time(ngi->authset, f));
SAFE(write_int16(ngi->authreason, f));
SAFE(write_int16(ngi->channelmax, f));
SAFE(write_int16(ngi->ajoin_count, f));
ARRAY_FOREACH (i, ngi->ajoin)
SAFE(write_string(ngi->ajoin[i], f));
SAFE(write_int16(ngi->memos.memomax, f));
SAFE(write_int16(ngi->ignore_count, f));
ARRAY_FOREACH (i, ngi->ignore)
SAFE(write_string(ngi->ignore[i], f));
SAFE(write_int16(ngi->language, f));
SAFE(write_int16(ngi->timezone, f));
SAFE(write_string(ngi->info, f));
SAFE(write_int16(ngi->os_priv, f));
if (ngi->os_priv >= NP_SERVADMIN) {
ARRAY_EXTEND(services_admins);
strscpy(services_admins[services_admins_count-1],
ngi_mainnick(ngi), NICKMAX);
} else if (ngi->os_priv >= NP_SERVOPER) {
ARRAY_EXTEND(services_opers);
strscpy(services_opers[services_opers_count-1],
ngi_mainnick(ngi), NICKMAX);
}
}
SAFE(write_int8(0, f));
SAFE(close_db(f));
sync_operserv_db(NULL); /* Write out new admin/oper lists */
return 0;
fail:
restore_db(f);
module_log_perror("Write error on %s", dbname);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", dbname, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
int close_nick_db(const char *dbname)
{
NickInfo *ni;
NickGroupInfo *ngi;
for (ni = first_nickinfo(); ni; ni = next_nickinfo())
del_nickinfo(ni);
for (ngi = first_nickgroupinfo(); ngi; ngi = next_nickgroupinfo())
del_nickgroupinfo(ngi);
return 0;
}
/*************************************************************************/
/********************** ChanServ database handling ***********************/
/*************************************************************************/
#undef HASHFUNC
#define HASHFUNC(key) (hashlookup[(uint8)((key)[1])]<<5 \
| ((key)[1] ? hashlookup[(uint8)((key)[2])] : 0))
#undef EXPIRE_CHECK
#define EXPIRE_CHECK(node) check_expire_channel(node)
#define add_channelinfo static _add_channelinfo
#define del_channelinfo static _del_channelinfo
DEFINE_HASH(channelinfo, ChannelInfo, name);
#undef add_channelinfo
#undef del_channelinfo
ChannelInfo *add_channelinfo(ChannelInfo *ci)
{
_add_channelinfo(ci);
return ci;
}
void del_channelinfo(ChannelInfo *ci)
{
_del_channelinfo(ci);
free_channelinfo(ci);
}
void put_channelinfo(ChannelInfo *ci) {}
/*************************************************************************/
/* Helper functions to convert between old and new channel levels. */
static inline int16 convert_old_level(int16 old)
{
if (old < 0)
return -convert_old_level(-old);/* avoid negative division */
else if (old <= 25)
return old*10; /* 0.. 25 -> 0..250 (10x) */
else if (old <= 50)
return 200 + old*2; /* 25.. 50 -> 250..300 ( 2x) */
else if (old <= 100)
return 280 + old*2/5; /* 50.. 100 -> 300..320 ( 0.4x) */
else if (old <= 1000)
return 300 + old/5; /* 100..1000 -> 320..500 ( 0.2x) */
else if (old <= 2000)
return 400 + old/10; /* 1000..2000 -> 500..600 ( 0.1x) */
else
return 500 + old/20; /* 2000..9999 -> 600..999 ( 0.05x) */
}
static inline int16 convert_new_level(int16 new)
{
if (new < 0)
return -convert_new_level(-new);/* avoid negative division */
else if (new <= 250)
return new/10; /* 0..250 -> 0.. 25 */
else if (new <= 300)
return new/2 - 100; /* 250..300 -> 25.. 50 */
else if (new <= 320)
return new*5/2 - 700; /* 300..320 -> 50.. 100 */
else if (new <= 500)
return new*5 - 1500; /* 320..500 -> 100..1000 */
else if (new <= 600)
return new*10 - 4000; /* 500..600 -> 1000..2000 */
else
return new*20 - 10000; /* 600..999 -> 2000..9980 */
}
/*************************************************************************/
static ChannelInfo *load_one_channel(dbFILE *f, int32 ver)
{
ChannelInfo *ci;
NickInfo *ni;
int16 tmp16, lev;
int32 tmp32;
int n_levels;
char *s;
int i;
int is_default; /* Are all access levels at defaults? */
ci = new_channelinfo();
SAFE(read_buffer(ci->name, f));
if (debug >= 2)
module_log("debug: loading channel %s", ci->name);
SAFE(read_string(&s, f));
if (s) {
ni = get_nickinfo(s);
if (ni)
ci->founder = ni->nickgroup;
free(s);
}
if (ver >= 7) {
SAFE(read_string(&s, f));
if (s) {
ni = get_nickinfo(s);
if (ni)
ci->successor = ni->nickgroup;
free(s);
}
/* Founder could be successor, which is bad, in vers 7,8 */
/* We can also have the case where two linked nicks were founder
* and successor, which would give them the same group ID in this
* version--remove the successor in this case as well */
if (ci->founder == ci->successor)
ci->successor = 0;
}
SAFE(read_buffer(ci->founderpass, f));
SAFE(read_string(&ci->desc, f));
if (!ci->desc)
ci->desc = sstrdup("");
SAFE(read_string(&ci->url, f));
SAFE(read_string(&ci->email, f));
SAFE(read_int32(&tmp32, f));
ci->time_registered = tmp32;
SAFE(read_int32(&tmp32, f));
ci->last_used = tmp32;
SAFE(read_string(&ci->last_topic, f));
SAFE(read_buffer(ci->last_topic_setter, f));
SAFE(read_int32(&tmp32, f));
ci->last_topic_time = tmp32;
SAFE(read_int32(&ci->flags, f));
if (ver >= 9)
SAFE(read_ptr((void **)&ci->suspendinfo, f));
if (ci->suspendinfo) {
SuspendInfo *si = smalloc(sizeof(*si));
SAFE(read_buffer(si->who, f));
SAFE(read_string(&si->reason, f));
SAFE(read_int32(&tmp32, f));
si->suspended = tmp32;
SAFE(read_int32(&tmp32, f));
si->expires = tmp32;
ci->suspendinfo = si;
}
ci->flags &= ~0x0100; /* 0x0100 = ENCRYPTEDPW */
SAFE(read_int16(&tmp16, f));
n_levels = tmp16;
reset_levels(ci, 1);
is_default = 1;
for (i = 0; i < n_levels; i++) {
SAFE(read_int16(&lev, f));
if (i >= CA_SIZE_4_5 || lev != def_levels_4_5[i])
is_default = 0;
if (i < CA_SIZE)
ci->levels[i] = convert_old_level(lev);
}
if (ci->levels[CA_AUTOPROTECT] == ACCLEV_INVALID)
ci->levels[CA_AUTOOWNER] = ACCLEV_INVALID;
else
ci->levels[CA_AUTOOWNER] = ACCLEV_FOUNDER;
/* Check whether all levels are at the default, and if so, remove the
* levels array */
if (is_default)
reset_levels(ci, 0);
SAFE(read_int16(&ci->access_count, f));
if (ci->access_count) {
ci->access = scalloc(ci->access_count, sizeof(ChanAccess));
ARRAY_FOREACH (i, ci->access) {
SAFE(read_int16(&tmp16, f)); /* in_use */
if (tmp16) {
SAFE(read_int16(&lev, f));
ci->access[i].level = convert_old_level(lev);
SAFE(read_string(&s, f));
if (s) {
ni = get_nickinfo(s);
if (ni)
ci->access[i].nickgroup = ni->nickgroup;
free(s);
}
}
}
} else {
ci->access = NULL;
}
SAFE(read_int16(&ci->akick_count, f));
if (ci->akick_count) {
ci->akick = scalloc(ci->akick_count, sizeof(AutoKick));
ARRAY_FOREACH (i, ci->akick) {
SAFE(read_int16(&tmp16, f)); /* in_use */
if (tmp16) {
SAFE(read_int16(&tmp16, f)); /* is_nick */
SAFE(read_string(&s, f));
if (tmp16) {
ci->akick[i].mask = smalloc(strlen(s)+5);
sprintf(ci->akick[i].mask, "%s!*@*", s);
free(s);
} else {
ci->akick[i].mask = s;
}
SAFE(read_string(&ci->akick[i].reason, f));
if (ver >= 8)
SAFE(read_buffer(ci->akick[i].who, f));
else
*ci->akick[i].who = 0;
time(&ci->akick[i].set);
ci->akick[i].lastused = 0;
} /* if (in_use) */
} /* for (i = 0..ci->akick_count-1) */
} else {
ci->akick = NULL;
}
if (ver < 10) {
SAFE(read_int16(&tmp16, f));
ci->mlock_on = tmp16;
SAFE(read_int16(&tmp16, f));
ci->mlock_off = tmp16;
} else {
SAFE(read_int32(&ci->mlock_on, f));
SAFE(read_int32(&ci->mlock_off, f));
}
SAFE(read_int32(&ci->mlock_limit, f));
SAFE(read_string(&ci->mlock_key, f));
ci->mlock_on &= ~chanmode_reg; /* check_modes() takes care of this */
SAFE(read_int16(&ci->memos.memos_count, f));
SAFE(read_int16(&ci->memos.memomax, f));
/* We may need to check memomax later (see load_one_nick()) */
if (ci->memos.memos_count) {
Memo *memos;
memos = smalloc(sizeof(Memo) * ci->memos.memos_count);
ci->memos.memos = memos;
ARRAY_FOREACH (i, ci->memos.memos) {
SAFE(read_uint32(&ci->memos.memos[i].number, f));
SAFE(read_int16(&ci->memos.memos[i].flags, f));
SAFE(read_int32(&tmp32, f));
ci->memos.memos[i].time = tmp32;
SAFE(read_buffer(ci->memos.memos[i].sender, f));
SAFE(read_string(&ci->memos.memos[i].text, f));
}
}
SAFE(read_string(&ci->entry_message, f));
ci->c = NULL;
return ci;
fail:
module_log("Read error on %s", f->filename);
return NULL;
}
/*************************************************************************/
/* Load extension data for a channel. */
static int load_one_channel_ext(dbFILE *f, int32 ver)
{
char *name;
ChannelInfo *ci;
ChannelInfo dummy_ci; /* for nonexisting channels */
int i;
int16 count;
SAFE(read_string(&name, f));
if (!name)
goto fail;
if (debug >= 2)
module_log("debug: loading channel extension %s", name);
if (!(ci = get_channelinfo(name))) {
module_log("Extension data found for nonexisting channel `%s'", name);
ci = &dummy_ci;
memset(ci, 0, sizeof(*ci));
}
free(name);
name = NULL;
SAFE(read_int16(&ci->memos.memomax, f));
if (ver >= 14) {
if (ver >= 23) {
SAFE(read_int16(&count, f));
if (count != ci->akick_count && ci != &dummy_ci) {
module_log("warning: autokick mismatch in extension data"
" for channel %s (corrupt database?): expected"
" %d, got %d", ci->name, ci->akick_count, count);
}
} else {
count = ci->akick_count;
}
for (i = 0; i < count; i++) {
if (i < ci->akick_count) {
SAFE(read_time(&ci->akick[i].set, f));
SAFE(read_time(&ci->akick[i].lastused, f));
} else {
time_t t;
SAFE(read_time(&t, f));
SAFE(read_time(&t, f));
}
}
}
if (ver >= 16) {
SAFE(read_string(&ci->mlock_link, f));
SAFE(read_string(&ci->mlock_flood, f));
}
if (ver >= 25)
SAFE(read_int32(&ci->mlock_joindelay, f));
if (ver >= 27) {
SAFE(read_int32(&ci->mlock_joinrate1, f));
SAFE(read_int32(&ci->mlock_joinrate2, f));
}
if (ver >= 20) {
int16 lev;
SAFE(read_int16(&count, f));
if (count) {
reset_levels(ci, 1);
for (i = 0; i < count; i++) {
SAFE(read_int16(&lev, f));
if (i < CA_SIZE)
ci->levels[i] = lev;
}
if (ver == 20) {
if (ci->levels[CA_AUTODEOP] == -10)
ci->levels[CA_AUTODEOP] = -1;
if (ci->levels[CA_NOJOIN] == -20)
ci->levels[CA_NOJOIN] = -100;
}
if (ci->levels[CA_AUTOPROTECT] == ACCLEV_INVALID)
ci->levels[CA_AUTOOWNER] = ACCLEV_INVALID;
else
ci->levels[CA_AUTOOWNER] = ACCLEV_FOUNDER;
} else {
free(ci->levels);
ci->levels = NULL;
}
SAFE(read_int16(&count, f));
if (count != ci->access_count && ci != &dummy_ci) {
module_log("warning: access count mismatch in extension data"
" for channel %s (corrupt database?): expected %d,"
" got %d", ci->name, ci->access_count, count);
}
for (i = 0; i < count; i++) {
SAFE(read_int16(&lev, f));
if (i < ci->access_count)
ci->access[i].level = lev;
}
}
if (ci == &dummy_ci) {
free(ci->mlock_link);
free(ci->mlock_flood);
free(ci->levels);
}
return 1;
fail:
module_log("Read error on %s", f->filename);
return 0;
}
/*************************************************************************/
static int chan_memomax_callback(Module *mod, const char *name)
{
ChannelInfo *ci;
#ifdef CLEAN_COMPILE
mod = mod;
#endif
if (strcmp(name, "memoserv/main") != 0)
return 0;
for (ci = first_channelinfo(); ci; ci = next_channelinfo()) {
if (ci->memos.memomax == MSMaxMemos)
ci->memos.memomax = MEMOMAX_DEFAULT;
}
/* We only need to do this once */
remove_callback(NULL, "load module", chan_memomax_callback);
return 0;
}
/*************************************************************************/
int open_channel_db(const char *dbname)
{
dbFILE *f;
int32 ver;
int i, c;
ChannelInfo *ci;
int failed = 0;
int need_memomax_check = 1;
/* Open database. */
if (!(f = my_open_db_r(dbname, &ver)))
return 1;
else if (f == OPENDB_ERROR)
return 0;
/* Load original data. */
for (i = 0; i < 256 && !failed; i++) {
while ((c = getc_db(f)) != 0) {
if (c != 1)
fatal("database/version4: Invalid format in %s", dbname);
ci = load_one_channel(f, ver);
if (ci) {
if (strcmp(ci->name, "#") == 0) {
module_log("Deleting unsupported channel \"#\"");
free_channelinfo(ci);
} else if (!(ci->flags & CI_VERBOTEN) && !ci->founder) {
/* Delete non-forbidden channels with no founder. These
* can crop up if the nick and channel databases get out
* of sync and the founder's nick has disappeared. Note
* that we ignore the successor here, but since this
* shouldn't happen normally anyway, no big deal.
*/
module_log("load channel database: Deleting founderless"
" channel %s", ci->name);
free_channelinfo(ci);
} else {
NickGroupInfo *ngi = get_nickgroupinfo(ci->founder);
if (ngi) {
ARRAY_EXTEND(ngi->channels);
strscpy(ngi->channels[ngi->channels_count-1],
ci->name, CHANMAX);
}
add_channelinfo(ci);
}
} else {
failed = 1;
break;
}
}
}
/* Load extension data if present. */
if (!failed && read_int32(&ver, f) == 0) {
if (ver <= FILE_VERSION || ver > LOCAL_VERSION)
fatal("database/version4: Invalid extension data version in %s",
dbname);
while ((c = getc_db(f)) != 0) {
if (c != 1)
fatal("database/version4: Invalid format in %s extension"
" data", dbname);
if ((failed = !load_one_channel_ext(f, ver)) != 0)
break;
}
need_memomax_check = 0;
}
if (ver < 26) {
/* Reset all AUTODEOP/NOJOIN levels to the defaults (version 4.x
* databases may have them set to non-default levels, and version 5
* doesn't allow them to be changed) */
for (ci = first_channelinfo(); ci; ci = next_channelinfo()) {
if (ci->levels) {
ci->levels[CA_AUTODEOP] = -1;
ci->levels[CA_NOJOIN] = -100;
}
}
}
/* Add the memomax check callback if needed. */
if (need_memomax_check)
add_callback(NULL, "load module", chan_memomax_callback);
/* Close database and return. */
close_db(f);
return !failed || forceload;
}
/*************************************************************************/
int sync_channel_db(const char *dbname)
{
dbFILE *f;
int i;
ChannelInfo *ci;
NickGroupInfo *ngi;
Memo *memos;
static time_t lastwarn = 0;
if (!(f = open_db(dbname, "w", 11)))
return 0;
for (ci = first_channelinfo(); ci; ci = next_channelinfo()) {
SAFE(write_int8(1, f));
SAFE(write_buffer(ci->name, f));
if (ci->founder && (ngi = get_ngi_id(ci->founder)))
SAFE(write_string(ngi_mainnick(ngi), f));
else
SAFE(write_string(NULL, f));
if (ci->successor && (ngi = get_ngi_id(ci->successor)))
SAFE(write_string(ngi_mainnick(ngi), f));
else
SAFE(write_string(NULL, f));
SAFE(write_buffer(ci->founderpass, f));
SAFE(write_string(ci->desc, f));
SAFE(write_string(ci->url, f));
SAFE(write_string(ci->email, f));
SAFE(write_int32(ci->time_registered, f));
SAFE(write_int32(ci->last_used, f));
SAFE(write_string(ci->last_topic, f));
SAFE(write_buffer(ci->last_topic_setter, f));
SAFE(write_int32(ci->last_topic_time, f));
SAFE(write_int32(ci->flags, f));
SAFE(write_ptr(ci->suspendinfo, f));
if (ci->suspendinfo) {
SAFE(write_buffer(ci->suspendinfo->who, f));
SAFE(write_string(ci->suspendinfo->reason, f));
SAFE(write_int32(ci->suspendinfo->suspended, f));
SAFE(write_int32(ci->suspendinfo->expires, f));
}
if (ci->levels) {
SAFE(write_int16(CA_SIZE, f));
for (i = 0; i < CA_SIZE; i++)
SAFE(write_int16(convert_new_level(ci->levels[i]), f));
} else {
SAFE(write_int16(CA_SIZE_4_5, f));
for (i = 0; i < CA_SIZE_4_5; i++) {
if (i == CA_NOJOIN && (ci->flags & CI_RESTRICTED))
SAFE(write_int16(0, f));
else
SAFE(write_int16(def_levels_4_5[i], f));
}
}
SAFE(write_int16(ci->access_count, f));
ARRAY_FOREACH (i, ci->access) {
if (ci->access[i].nickgroup)
ngi = get_ngi_id(ci->access[i].nickgroup);
else
ngi = NULL;
SAFE(write_int16(ngi != NULL, f));
if (ngi) {
SAFE(write_int16(convert_new_level(ci->access[i].level), f));
SAFE(write_string(ngi_mainnick(ngi), f));
}
}
SAFE(write_int16(ci->akick_count, f));
ARRAY_FOREACH (i, ci->akick) {
SAFE(write_int16((ci->akick[i].mask != NULL), f)); /* in_use */
if (ci->akick[i].mask) {
SAFE(write_int16(0, f)); /* is_nick */
SAFE(write_string(ci->akick[i].mask, f));
SAFE(write_string(ci->akick[i].reason, f));
SAFE(write_buffer(ci->akick[i].who, f));
}
}
SAFE(write_int32(ci->mlock_on, f));
SAFE(write_int32(ci->mlock_off, f));
SAFE(write_int32(ci->mlock_limit, f));
SAFE(write_string(ci->mlock_key, f));
SAFE(write_int16(ci->memos.memos_count, f));
/* 4.x don't understand _DEFAULT (see sync_nick_db()) */
if (ci->memos.memomax == MEMOMAX_DEFAULT)
SAFE(write_int16(MSMaxMemos, f));
else
SAFE(write_int16(ci->memos.memomax, f));
memos = ci->memos.memos;
ARRAY_FOREACH (i, ci->memos.memos) {
SAFE(write_int32(ci->memos.memos[i].number, f));
SAFE(write_int16(ci->memos.memos[i].flags, f));
SAFE(write_int32(ci->memos.memos[i].time, f));
SAFE(write_buffer(ci->memos.memos[i].sender, f));
SAFE(write_string(ci->memos.memos[i].text, f));
}
SAFE(write_string(ci->entry_message, f));
} /* for (ci) */
{
/* This is an UGLY HACK but it simplifies loading. */
static char buf[256];
SAFE(write_buffer(buf, f));
}
SAFE(write_int32(LOCAL_VERSION, f));
for (ci = first_channelinfo(); ci; ci = next_channelinfo()) {
SAFE(write_int8(1, f));
SAFE(write_string(ci->name, f));
SAFE(write_int16(ci->memos.memomax, f));
SAFE(write_int16(ci->akick_count, f));
ARRAY_FOREACH (i, ci->akick) {
SAFE(write_time(ci->akick[i].set, f));
SAFE(write_time(ci->akick[i].lastused, f));
}
SAFE(write_string(ci->mlock_link, f));
SAFE(write_string(ci->mlock_flood, f));
SAFE(write_int32(ci->mlock_joindelay, f));
SAFE(write_int32(ci->mlock_joinrate1, f));
SAFE(write_int32(ci->mlock_joinrate2, f));
if (ci->levels) {
SAFE(write_int16(CA_SIZE, f));
for (i = 0; i < CA_SIZE; i++)
SAFE(write_int16(ci->levels[i], f));
} else {
SAFE(write_int16(0, f));
}
SAFE(write_int16(ci->access_count, f));
ARRAY_FOREACH (i, ci->access)
SAFE(write_int16(ci->access[i].level, f));
}
SAFE(write_int8(0, f));
SAFE(close_db(f));
return 0;
fail:
restore_db(f);
module_log_perror("Write error on %s", dbname);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", dbname, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
int close_channel_db(const char *dbname)
{
ChannelInfo *ci;
for (ci = first_channelinfo(); ci; ci = next_channelinfo())
del_channelinfo(ci);
return 0;
}
/*************************************************************************/
/********************** OperServ database handling ***********************/
/*************************************************************************/
/* Local copies of OperServ data. */
static int local_maxusercnt;
static time_t local_maxusertime;
static int8 local_no_supass;
static char local_supass[PASSMAX];
/*************************************************************************/
int get_operserv_data(int what, void *ret)
{
switch (what) {
case OSDATA_MAXUSERCNT:
*((int *)ret) = local_maxusercnt;
return 1;
case OSDATA_MAXUSERTIME:
*((time_t *)ret) = local_maxusertime;
return 1;
case OSDATA_SUPASS:
if (local_no_supass)
*((char **)ret) = NULL;
else
*((char **)ret) = local_supass;
return 1;
}
return 0;
}
/*************************************************************************/
int put_operserv_data(int what, void *ptr)
{
switch (what) {
case OSDATA_MAXUSERCNT:
local_maxusercnt = *((int *)ptr);
return 1;
case OSDATA_MAXUSERTIME:
local_maxusertime = *((time_t *)ptr);
return 1;
case OSDATA_SUPASS:
if (ptr) {
local_no_supass = 0;
memcpy(local_supass, ptr, PASSMAX);
} else {
local_no_supass = 1;
memset(local_supass, 0, PASSMAX);
}
return 1;
}
return 0;
}
/*************************************************************************/
int open_operserv_db(const char *dbname)
{
dbFILE *f;
int32 ver;
int16 i, n;
char *s;
if (!(f = my_open_db_r(dbname, &ver)))
return 1;
else if (f == OPENDB_ERROR)
return 0;
services_admins = services_opers = NULL;
services_admins_count = services_opers_count = 0;
local_maxusercnt = 0;
local_maxusertime = 0;
local_no_supass = 1;
memset(local_supass, 0, sizeof(local_supass));
SAFE(read_int16(&n, f));
for (i = 0; i < n; i++) {
SAFE(read_string(&s, f));
if (s && i < MAX_SERVADMINS) {
ARRAY_EXTEND(services_admins);
strscpy(services_admins[services_admins_count-1], s, NICKMAX);
}
free(s);
}
SAFE(read_int16(&n, f));
for (i = 0; i < n; i++) {
SAFE(read_string(&s, f));
if (s && i < MAX_SERVOPERS) {
ARRAY_EXTEND(services_opers);
strscpy(services_opers[services_opers_count-1], s, NICKMAX);
}
free(s);
}
if (ver >= 7) {
int32 tmp32;
SAFE(read_int32(&local_maxusercnt, f));
SAFE(read_int32(&tmp32, f));
local_maxusertime = tmp32;
}
if (ver >= 9) {
SAFE(read_int8(&local_no_supass, f));
if (!local_no_supass)
SAFE(read_buffer(local_supass, f));
}
close_db(f);
return 1;
fail:
close_db(f);
module_log("Read error on %s", dbname);
return 0;
}
/*************************************************************************/
int sync_operserv_db(const char *dbname)
{
dbFILE *f;
int16 i;
static time_t lastwarn = 0;
/* To avoid extraneous writes when using NickServ (which calls us with
* NULL to write the DB after updating services_{admins,opers}): */
static const char *last_dbname = PTR_INVALID;
const char *last = last_dbname;
last_dbname = dbname;
if (last == NULL && dbname == NULL) {
/* This occurs when NickServ is unloaded after a DB update; since
* presumably OperServ will soon be unloaded as well, and in any
* case we don't have a DB name to do anything with, just return. */
return 0;
}
if (last == NULL && dbname != NULL) {
/* Previous call was by NickServ and this one wasn't, so skip */
return 0;
}
if (dbname == NULL)
dbname = last;
if (!(f = open_db(dbname, "w", 11)))
return 0;
SAFE(write_int16(services_admins_count, f));
ARRAY_FOREACH (i, services_admins)
SAFE(write_string(services_admins[i], f));
SAFE(write_int16(services_opers_count, f));
ARRAY_FOREACH (i, services_opers)
SAFE(write_string(services_opers[i], f));
SAFE(write_int32(local_maxusercnt, f));
SAFE(write_int32(local_maxusertime, f));
SAFE(write_int8(local_no_supass, f));
if (!local_no_supass)
SAFE(write_buffer(local_supass, f));
SAFE(close_db(f));
return 0;
fail:
restore_db(f);
module_log_perror("Write error on %s", dbname);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", dbname, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
int close_operserv_db(const char *dbname)
{
free(services_admins);
services_admins = NULL;
services_admins_count = 0;
free(services_opers);
services_opers = NULL;
services_opers_count = 0;
return 0;
}
/*************************************************************************/
/************************ News database handling *************************/
/*************************************************************************/
static int32 newslist_count = 0;
static NewsItem *newslist = NULL;
static int newslist_iterator;
/*************************************************************************/
NewsItem *add_news(NewsItem *newsitem)
{
if (newslist_count >= MAX_NEWS)
fatal("add_news(): too many news items!");
ARRAY_EXTEND(newslist);
memcpy(&newslist[newslist_count-1], newsitem, sizeof(NewsItem));
newslist[newslist_count-1].next = (NewsItem *)(long)(newslist_count-1); /*index*/
free(newsitem);
return &newslist[newslist_count-1];
}
/*************************************************************************/
void del_news(NewsItem *newsitem)
{
int num = (int)(long)(newsitem->next);
if (num < 0 || num >= newslist_count) {
module_log("del_news(): invalid index %d in news item at %p",
num, newsitem);
return;
}
free(newsitem->text);
ARRAY_REMOVE(newslist, num);
if (num < newslist_iterator)
newslist_iterator--;
while (num < newslist_count) {
newslist[num].next = (NewsItem *)(long)num;
num++;
}
}
/*************************************************************************/
NewsItem *get_news(int16 type, int32 num)
{
int i;
ARRAY_FOREACH (i, newslist) {
if (newslist[i].type == type && newslist[i].num == num)
break;
}
return i<newslist_count ? &newslist[i] : NULL;
}
/*************************************************************************/
void put_news(NewsItem *news)
{
/* nothing */
}
/*************************************************************************/
NewsItem *first_news(void)
{
newslist_iterator = 0;
return next_news();
}
NewsItem *next_news(void)
{
if (newslist_iterator >= newslist_count)
return NULL;
return &newslist[newslist_iterator++];
}
/*************************************************************************/
int news_count(void)
{
return newslist_count;
}
/*************************************************************************/
int open_news_db(const char *dbname)
{
dbFILE *f;
int32 ver;
int i;
int16 tmp16;
if (!(f = my_open_db_r(dbname, &ver)))
return 1;
else if (f == OPENDB_ERROR)
return 0;
read_int16(&tmp16, f);
newslist_count = tmp16;
if (newslist_count > MAX_NEWS)
newslist_count = MAX_NEWS;
newslist = scalloc(sizeof(*newslist), newslist_count);
for (i = 0; i < tmp16; i++) {
int32 tmp32;
if (i < newslist_count) { /* watch out for stored count > MAX_NEWS */
newslist[i].next = (NewsItem *) (long)i;
SAFE(read_int16(&newslist[i].type, f));
SAFE(read_int32(&newslist[i].num, f));
SAFE(read_string(&newslist[i].text, f));
SAFE(read_buffer(newslist[i].who, f));
SAFE(read_int32(&tmp32, f));
newslist[i].time = tmp32;
} else {
int16 tmp16_2;
char *s, t[NICKMAX];
SAFE(read_int16(&tmp16_2, f));
SAFE(read_int32(&tmp32, f));
SAFE(read_string(&s, f));
free(s);
SAFE(read_buffer(t, f));
SAFE(read_int32(&tmp32, f));
}
}
close_db(f);
return 1;
fail:
close_db(f);
module_log("Read error on %s", dbname);
return 0;
}
/*************************************************************************/
int sync_news_db(const char *dbname)
{
dbFILE *f;
int16 i;
static time_t lastwarn = 0;
if (!(f = open_db(dbname, "w", 11)))
return 0;
write_int16(newslist_count, f);
ARRAY_FOREACH (i, newslist) {
SAFE(write_int16(newslist[i].type, f));
SAFE(write_int32(newslist[i].num, f));
SAFE(write_string(newslist[i].text, f));
SAFE(write_buffer(newslist[i].who, f));
SAFE(write_int32(newslist[i].time, f));
}
SAFE(close_db(f));
return 0;
fail:
restore_db(f);
module_log_perror("Write error on %s", dbname);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", dbname, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
int close_news_db(const char *dbname)
{
int i;
ARRAY_FOREACH (i, newslist)
free(newslist[i].text);
free(newslist);
newslist = NULL;
newslist_count = 0;
return 0;
}
/*************************************************************************/
/******************* OperServ MaskData base functions ********************/
/*************************************************************************/
static MaskData *masklist[256];
static int32 masklist_count[256];
static int masklist_iterator[256];
/*************************************************************************/
MaskData *add_maskdata(uint8 type, MaskData *data)
{
int num = masklist_count[type];
if (num >= MAX_MASKDATA)
fatal("add_maskdata(): too many items for type %u", type);
ARRAY2_EXTEND(masklist[type], masklist_count[type]);
memcpy(&masklist[type][num], data, sizeof(*data));
masklist[type][num].next = (MaskData *)(long)num; /* use as index */
free(data);
return &masklist[type][num];
}
/*************************************************************************/
void del_maskdata(uint8 type, MaskData *data)
{
int num = (int)(long)(data->next);
if (num < 0 || num >= masklist_count[type]) {
module_log("del_maskdata(): invalid index %d for type %u at %p",
num, type, data);
return;
}
free(data->mask);
free(data->reason);
ARRAY2_REMOVE(masklist[type], masklist_count[type], num);
if (num < masklist_iterator[type])
masklist_iterator[type]--;
while (num < masklist_count[type]) {
masklist[type][num].next = (MaskData *)(long)num;
num++;
}
}
/*************************************************************************/
MaskData *get_maskdata(uint8 type, const char *mask)
{
int i;
MaskData *result;
ARRAY2_SEARCH(masklist[type],masklist_count[type],mask,mask,stricmp,i);
if (i >= masklist_count[type])
return NULL;
result = &masklist[type][i];
return !noexpire && check_expire_maskdata(type,result) ? NULL : result;
}
/*************************************************************************/
MaskData *get_matching_maskdata(uint8 type, const char *str)
{
int i;
ARRAY2_FOREACH (i, masklist[type], masklist_count[type]) {
if (match_wild_nocase(masklist[type][i].mask, str)) {
MaskData *result = &masklist[type][i];
if (noexpire || !check_expire_maskdata(type,result))
return result;
else
i--;
}
}
return NULL;
}
/*************************************************************************/
void put_maskdata(uint8 type, MaskData *data)
{
/* nothing */
}
/*************************************************************************/
MaskData *first_maskdata(uint8 type)
{
masklist_iterator[type] = 0;
return next_maskdata(type);
}
MaskData *next_maskdata(uint8 type)
{
MaskData *result;
do {
if (masklist_iterator[type] >= masklist_count[type])
return NULL;
result = &masklist[type][masklist_iterator[type]++];
} while (!noexpire && check_expire_maskdata(type, result));
return result;
}
/*************************************************************************/
int maskdata_count(uint8 type)
{
return masklist_count[type];
}
/*************************************************************************/
static int read_maskdata(uint8 type, const char *dbname, dbFILE *f)
{
int32 ver;
int i;
int16 tmp16;
MaskData *list;
int count;
read_int16(&tmp16, f);
count = tmp16;
if (count > MAX_MASKDATA)
count = MAX_MASKDATA;
list = scalloc(sizeof(*list), count);
for (i = 0; i < tmp16; i++) {
int32 tmp32;
if (i < count) { /* watch out for # of entries > MAX_MASKDATA */
list[i].next = (MaskData *) (long)i;
SAFE(read_string(&list[i].mask, f));
if (type == MD_EXCEPTION) {
SAFE(read_int16(&list[i].limit, f));
SAFE(read_buffer(list[i].who, f));
SAFE(read_string(&list[i].reason, f));
} else {
SAFE(read_string(&list[i].reason, f));
SAFE(read_buffer(list[i].who, f));
}
SAFE(read_int32(&tmp32, f));
list[i].time = tmp32;
SAFE(read_int32(&tmp32, f));
list[i].expires = tmp32;
list[i].num = i+1;
} else {
char *s, t[NICKMAX];
SAFE(read_string(&s, f));
free(s);
if (type == MD_EXCEPTION) {
SAFE(read_int16(&tmp16, f));
SAFE(read_buffer(t, f));
SAFE(read_string(&s, f));
} else {
SAFE(read_string(&s, f));
SAFE(read_buffer(t, f));
}
free(s);
SAFE(read_int32(&tmp32, f));
SAFE(read_int32(&tmp32, f));
}
}
if (read_int32(&ver, f) == 0) {
if (ver <= FILE_VERSION || ver > LOCAL_VERSION)
fatal("database/version4: Invalid extension data version in %s",
dbname);
for (i = 0; i < count; i++) {
SAFE(read_time(&list[i].time, f));
SAFE(read_time(&list[i].expires, f));
SAFE(read_time(&list[i].lastused, f));
}
}
masklist[type] = list;
masklist_count[type] = count;
return 1;
fail:
close_db(f);
module_log("Read error on %s", dbname);
return 0;
}
/*************************************************************************/
static int write_maskdata(uint8 type, const char *dbname, dbFILE *f)
{
int16 i;
static time_t lastwarn[256];
MaskData *list = masklist[type];
int count;
count = masklist_count[type];
write_int16(count, f);
for (i = 0; i < count; i++) {
SAFE(write_string(list[i].mask, f));
if (type == MD_EXCEPTION) {
SAFE(write_int16(list[i].limit, f));
SAFE(write_buffer(list[i].who, f));
SAFE(write_string(list[i].reason, f));
} else {
SAFE(write_string(list[i].reason, f));
SAFE(write_buffer(list[i].who, f));
}
SAFE(write_int32(list[i].time, f));
SAFE(write_int32(list[i].expires, f));
}
SAFE(write_int32(LOCAL_VERSION, f));
for (i = 0; i < count; i++) {
SAFE(write_time(list[i].time, f));
SAFE(write_time(list[i].expires, f));
SAFE(write_time(list[i].lastused, f));
/* _Now_ check for expiration. Doing it this way means the record
* is stored one more time than necessary, but also means we don't
* have to seek back to the beginning of the file and rewrite the
* count. */
if (check_expire_maskdata(type, &list[i])) {
i--;
count--;
}
}
return 1;
fail:
restore_db(f);
module_log_perror("Write error on %s", dbname);
if (time(NULL) - lastwarn[type] > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", dbname, strerror(errno));
lastwarn[type] = time(NULL);
}
return 0;
}
/*************************************************************************/
static void free_all_maskdata(uint8 type)
{
int i;
for (i = 0; i < masklist_count[type]; i++) {
free(masklist[type][i].mask);
free(masklist[type][i].reason);
}
free(masklist[type]);
masklist[type] = NULL;
masklist_count[type] = 0;
}
/*************************************************************************/
/********************** Autokill database handling ***********************/
/*************************************************************************/
int open_akill_db(const char *dbname)
{
dbFILE *f;
int32 ver;
if (!(f = my_open_db_r(dbname, &ver)))
return 1;
else if (f == OPENDB_ERROR)
return 0;
if (!read_maskdata(MD_AKILL, dbname, f))
return 0;
if (getc_db(f) == 1) {
if (!read_maskdata(MD_EXCLUSION, dbname, f))
return 0;
}
close_db(f);
return 1;
}
/*************************************************************************/
int sync_akill_db(const char *dbname)
{
dbFILE *f;
static time_t lastwarn = 0;
if (!(f = open_db(dbname, "w", 11)))
return 0;
if (!write_maskdata(MD_AKILL, dbname, f))
return 0;
SAFE(write_int8(1, f));
if (!write_maskdata(MD_EXCLUSION, dbname, f))
return 0;
SAFE(close_db(f));
return 0;
fail:
restore_db(f);
module_log_perror("Write error on %s", dbname);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", dbname, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
int close_akill_db(const char *dbname)
{
free_all_maskdata(MD_AKILL);
free_all_maskdata(MD_EXCLUSION);
return 0;
}
/*************************************************************************/
/********************** Exception database handling **********************/
/*************************************************************************/
MaskData *get_exception_by_num(int num)
{
int i;
MaskData *result;
ARRAY2_SEARCH_SCALAR(masklist[MD_EXCEPTION], masklist_count[MD_EXCEPTION],
num, num, i);
if (i >= masklist_count[MD_EXCEPTION])
return NULL;
result = &masklist[MD_EXCEPTION][i];
return !noexpire && check_expire_maskdata(MD_EXCEPTION, result)
? NULL : result;
}
/*************************************************************************/
/* Move an exception so it has the given number; we can assume that no
* other exception already has that number.
*/
MaskData *move_exception(MaskData *except, int newnum)
{
int count; /* shortcut for "masklist_count[MD_EXCEPTION]" */
int index; /* index of `except' */
int newindex; /* where `except' should be moved to */
MaskData tmp; /* to save the data while we move it around */
count = masklist_count[MD_EXCEPTION];
index = except - masklist[MD_EXCEPTION];
if ((index == 0 || except[-1].num < newnum)
&& (index == count-1 || except[1].num >= newnum)
) {
/* New number is already between previous and next entries; no need
* to move it around, just renumber and exit */
except->num = newnum;
while (++index < count && except[1].num == except[0].num) {
except[1].num++;
except++;
}
return except;
}
/* Save the old exception data and remove it from the array */
tmp = *except;
if (index < count-1)
memmove(except, except+1, sizeof(*except) * ((count-1)-index));
/* Find where the exception should go */
for (newindex = 0; newindex < count-1; newindex++) {
if (masklist[MD_EXCEPTION][newindex].num >= newnum)
break;
}
/* Sanity check--this case should have been caught above */
if (index == newindex) {
module_log("BUG: move_exception didn't catch index == newindex for"
" exception %d!", newnum);
}
/* Actually put it where it belongs */
except = &masklist[MD_EXCEPTION][newindex];
if (newindex < count-1)
memmove(except+1, except, sizeof(*except) * ((count-1)-newindex));
*except = tmp;
except->num = newnum;
/* Increment following exception number as needed */
for (index = newindex+1; index < count; index++) {
if (except[1].num == except[0].num)
except[1].num++;
else
break;
except++;
}
return &masklist[MD_EXCEPTION][newindex];
}
/*************************************************************************/
int open_exception_db(const char *dbname)
{
dbFILE *f;
int32 ver;
if (!(f = my_open_db_r(dbname, &ver)))
return 1;
else if (f == OPENDB_ERROR)
return 0;
if (!read_maskdata(MD_EXCEPTION, dbname, f))
return 0;
close_db(f);
return 1;
}
/*************************************************************************/
int sync_exception_db(const char *dbname)
{
dbFILE *f;
static time_t lastwarn = 0;
if (!(f = open_db(dbname, "w", 11)))
return 0;
if (!write_maskdata(MD_EXCEPTION, dbname, f))
return 0;
SAFE(close_db(f));
return 0;
fail:
restore_db(f);
module_log_perror("Write error on %s", dbname);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", dbname, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
int close_exception_db(const char *dbname)
{
free_all_maskdata(MD_EXCEPTION);
return 0;
}
/*************************************************************************/
/*********************** S-line database handling ************************/
/*************************************************************************/
int open_sline_db(const char *dbname)
{
dbFILE *f;
int32 ver;
if (!(f = my_open_db_r(dbname, &ver)))
return 1;
else if (f == OPENDB_ERROR)
return 0;
if (!read_maskdata(MD_SGLINE, dbname, f))
return 0;
if (!read_maskdata(MD_SQLINE, dbname, f))
return 0;
if (!read_maskdata(MD_SZLINE, dbname, f))
return 0;
close_db(f);
return 1;
}
/*************************************************************************/
int sync_sline_db(const char *dbname)
{
dbFILE *f;
static time_t lastwarn = 0;
if (!(f = open_db(dbname, "w", 11)))
return 0;
if (!write_maskdata(MD_SGLINE, dbname, f))
return 0;
if (!write_maskdata(MD_SQLINE, dbname, f))
return 0;
if (!write_maskdata(MD_SZLINE, dbname, f))
return 0;
SAFE(close_db(f));
return 0;
fail:
restore_db(f);
module_log_perror("Write error on %s", dbname);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", dbname, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
int close_sline_db(const char *dbname)
{
free_all_maskdata(MD_SGLINE);
free_all_maskdata(MD_SQLINE);
free_all_maskdata(MD_SZLINE);
return 0;
}
/*************************************************************************/
/********************** StatServ database handling ***********************/
/*************************************************************************/
#undef HASHFUNC
#define HASHFUNC(key) (hashlookup[(uint8)((key)[0])]<<5 \
| ((key)[0] ? hashlookup[(uint8)((key)[1])] : 0))
#undef EXPIRE_CHECK
#define EXPIRE_CHECK(node) 0
#define add_serverstats static _add_serverstats
#define del_serverstats static _del_serverstats
DEFINE_HASH(serverstats, ServerStats, name);
#undef add_serverstats
#undef del_serverstats
ServerStats *add_serverstats(ServerStats *ss)
{
_add_serverstats(ss);
return ss;
}
void del_serverstats(ServerStats *ss)
{
_del_serverstats(ss);
free_serverstats(ss);
}
void put_serverstats(ServerStats *ss) {}
/*************************************************************************/
static ServerStats *load_one_serverstats(dbFILE *f)
{
ServerStats *ss;
char *servername;
int32 tmp32;
SAFE(read_string(&servername, f));
ss = new_serverstats(servername);
free(servername);
servername = NULL;
SAFE(read_int32(&tmp32, f));
ss->t_join = tmp32;
SAFE(read_int32(&tmp32, f)); /* t_quit */
/* Avoid join>=quit staying true on load (which would indicate that the
* server is online even before any server connections are processed) */
ss->t_quit = time(NULL)-1;
if (ss->t_join >= ss->t_quit)
ss->t_join = ss->t_quit-1;
SAFE(read_string(&ss->quit_message, f));
return ss;
fail:
module_log("Read error on %s", f->filename);
return NULL;
}
/*************************************************************************/
static int load_one_serverstats_ext(dbFILE *f, int32 ver)
{
ServerStats *ss;
char *servername;
SAFE(read_string(&servername, f));
if (!servername)
goto fail;
ss = get_serverstats(servername);
if (!ss) {
module_log("Extension data found for nonexisting server `%s'",
servername);
free(servername);
return 0;
}
free(servername);
SAFE(read_time(&ss->t_join, f));
return 1;
fail:
module_log("Read error on %s", f->filename);
return 0;
}
/*************************************************************************/
int open_statserv_db(const char *dbname)
{
dbFILE *f;
int32 ver, i, nservers;
int16 tmp16;
int failed = 0;
ServerStats *ss;
/* Open database. */
if (!(f = my_open_db_r(dbname, &ver)))
return 1;
else if (f == OPENDB_ERROR)
return 0;
/* Load original data. */
SAFE(read_int16(&tmp16, f));
nservers = tmp16;
for (i = 0; i < nservers && !failed; i++) {
ss = load_one_serverstats(f);
if (ss)
add_serverstats(ss);
else
failed = 1;
}
/* Load extension data if present. */
if (!failed && read_int32(&ver, f) == 0) {
int32 moreservers;
if (ver <= FILE_VERSION || ver > LOCAL_VERSION)
fatal("database/version4: Invalid extension data version in %s",
dbname);
SAFE(read_int32(&moreservers, f));
for (i = 0; i < moreservers && !failed; i++) {
ss = load_one_serverstats(f);
if (ss)
add_serverstats(ss);
else
failed = 1;
}
nservers += moreservers;
for (i = 0; i < nservers && !failed; i++)
failed = !load_one_serverstats_ext(f, ver);
}
/* Close database and return. */
close_db(f);
return !failed || forceload;
fail:
close_db(f);
module_log("Read error on %s", dbname);
return 0;
}
/*************************************************************************/
int sync_statserv_db(const char *dbname)
{
dbFILE *f;
int32 count, realcount, i;
ServerStats *ss;
static time_t lastwarn = 0;
if (!(f = open_db(dbname, "w", 11)))
return 0;
realcount = 0;
for (ss = first_serverstats(); ss; ss = next_serverstats())
realcount++;
if (realcount > 32767) /* Well, you never know... */
count = 32767;
else
count = realcount;
SAFE(write_int16((int16)count, f));
for (ss = first_serverstats(), i = 0; i < count;
ss = next_serverstats(), i++
) {
if (!ss) {
module_log("BUG: sync_statserv_db(): ss NULL but i < count!");
wallops(NULL, "Error saving %s! Please check log file.", dbname);
restore_db(f);
return 0;
}
SAFE(write_string(ss->name, f));
SAFE(write_int32(ss->t_join, f));
SAFE(write_int32(ss->t_quit, f));
SAFE(write_string(ss->quit_message, f));
}
SAFE(write_int32(LOCAL_VERSION, f));
if (realcount > count) {
SAFE(write_int32(realcount-count, f));
for (; i < realcount; ss = next_serverstats(), i++) {
if (!ss) {
module_log("BUG: sync_statserv_db(): ss NULL but i <"
" realcount!");
wallops(NULL, "Error saving %s! Please check log file.",
dbname);
restore_db(f);
return 0;
}
SAFE(write_string(ss->name, f));
SAFE(write_int32(ss->t_join, f));
SAFE(write_int32(ss->t_quit, f));
SAFE(write_string(ss->quit_message, f));
}
} else {
SAFE(write_int32(0, f));
}
for (ss = first_serverstats(); ss; ss = next_serverstats()) {
SAFE(write_string(ss->name, f));
SAFE(write_time(ss->t_join, f));
}
SAFE(close_db(f));
return 0;
fail:
restore_db(f);
module_log_perror("Write error on %s", dbname);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", dbname, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
int close_statserv_db(const char *dbname)
{
ServerStats *ss;
for (ss = first_serverstats(); ss; ss = next_serverstats())
del_serverstats(ss);
return 0;
}
/*************************************************************************/
/********************** OperServ STATS ALL callback **********************/
/*************************************************************************/
static int do_stats_all(User *user, const char *s_OperServ)
{
int32 count, mem;
int i;
NickGroupInfo *ngi;
NickInfo *ni;
ChannelInfo *ci;
NewsItem *news;
MaskData *md;
ServerStats *ss;
count = mem = 0;
for (ni = first_nickinfo(); ni; ni = next_nickinfo()) {
count++;
mem += sizeof(*ni);
if (ni->last_usermask)
mem += strlen(ni->last_usermask)+1;
if (ni->last_realmask)
mem += strlen(ni->last_realmask)+1;
if (ni->last_realname)
mem += strlen(ni->last_realname)+1;
if (ni->last_quit)
mem += strlen(ni->last_quit)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_NICKINFO_MEM,
count, (mem+512) / 1024);
count = mem = 0;
for (ngi = first_nickgroupinfo(); ngi; ngi = next_nickgroupinfo()) {
count++;
mem += sizeof(*ngi);
if (ngi->url)
mem += strlen(ngi->url)+1;
if (ngi->email)
mem += strlen(ngi->email)+1;
if (ngi->info)
mem += strlen(ngi->info)+1;
if (ngi->suspendinfo) {
mem += sizeof(SuspendInfo);
if (ngi->suspendinfo->reason)
mem += strlen(ngi->suspendinfo->reason)+1;
}
mem += sizeof(channame_t) * ngi->channels_count;
mem += sizeof(char *) * ngi->access_count;
ARRAY_FOREACH (i, ngi->access) {
if (ngi->access[i])
mem += strlen(ngi->access[i])+1;
}
mem += sizeof(char *) * ngi->ajoin_count;
ARRAY_FOREACH (i, ngi->ajoin) {
if (ngi->ajoin[i])
mem += strlen(ngi->ajoin[i])+1;
}
mem += sizeof(Memo) * ngi->memos.memos_count;
ARRAY_FOREACH (i, ngi->memos.memos) {
if (ngi->memos.memos[i].text)
mem += strlen(ngi->memos.memos[i].text)+1;
}
mem += sizeof(char *) * ngi->ignore_count;
ARRAY_FOREACH (i, ngi->ignore) {
if (ngi->ignore[i])
mem += strlen(ngi->ignore[i])+1;
}
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_NICKGROUPINFO_MEM,
count, (mem+512) / 1024);
count = mem = 0;
for (ci = first_channelinfo(); ci; ci = next_channelinfo()) {
count++;
mem += sizeof(*ci);
if (ci->desc)
mem += strlen(ci->desc)+1;
if (ci->url)
mem += strlen(ci->url)+1;
if (ci->email)
mem += strlen(ci->email)+1;
if (ci->last_topic)
mem += strlen(ci->last_topic)+1;
if (ci->suspendinfo) {
mem += sizeof(SuspendInfo);
if (ci->suspendinfo->reason)
mem += strlen(ci->suspendinfo->reason)+1;
}
if (ci->levels)
mem += sizeof(*ci->levels) * CA_SIZE;
mem += ci->access_count * sizeof(ChanAccess);
mem += ci->akick_count * sizeof(AutoKick);
ARRAY_FOREACH (i, ci->akick) {
if (ci->akick[i].mask)
mem += strlen(ci->akick[i].mask)+1;
if (ci->akick[i].reason)
mem += strlen(ci->akick[i].reason)+1;
}
if (ci->mlock_key)
mem += strlen(ci->mlock_key)+1;
if (ci->mlock_link)
mem += strlen(ci->mlock_link)+1;
if (ci->mlock_flood)
mem += strlen(ci->mlock_flood)+1;
if (ci->entry_message)
mem += strlen(ci->entry_message)+1;
mem += sizeof(Memo) * ci->memos.memos_count;
ARRAY_FOREACH (i, ci->memos.memos) {
if (ci->memos.memos[i].text)
mem += strlen(ci->memos.memos[i].text)+1;
}
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_CHANSERV_MEM,
count, (mem+512) / 1024);
count = mem = 0;
for (ss = first_serverstats(); ss; ss = next_serverstats()) {
count++;
mem += sizeof(*ss) + strlen(ss->name)+1;
if (ss->quit_message)
mem += strlen(ss->quit_message)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_STATSERV_MEM,
count, (mem+512) / 1024);
count = mem = 0;
for (news = first_news(); news; news = next_news()) {
count++;
mem += sizeof(*news);
if (news->text)
mem += strlen(news->text)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_NEWS_MEM,
count, (mem+512) / 1024);
count = mem = 0;
for (md = first_maskdata(MD_AKILL); md; md = next_maskdata(MD_AKILL)) {
count++;
mem += sizeof(*md);
if (md->mask)
mem += strlen(md->mask)+1;
if (md->reason)
mem += strlen(md->reason)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_AKILL_MEM,
count, (mem+512) / 1024);
count = mem = 0;
for (md=first_maskdata(MD_EXCEPTION); md; md=next_maskdata(MD_EXCEPTION)) {
count++;
mem += sizeof(*md);
if (md->mask)
mem += strlen(md->mask)+1;
if (md->reason)
mem += strlen(md->reason)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_EXCEPTION_MEM,
count, (mem+512) / 1024);
count = mem = 0;
for (md = first_maskdata(MD_SGLINE); md; md = next_maskdata(MD_SGLINE)) {
count++;
mem += sizeof(*md);
if (md->mask)
mem += strlen(md->mask)+1;
if (md->reason)
mem += strlen(md->reason)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_SGLINE_MEM,
count, (mem+512) / 1024);
count = mem = 0;
for (md = first_maskdata(MD_SQLINE); md; md = next_maskdata(MD_SQLINE)) {
count++;
mem += sizeof(*md);
if (md->mask)
mem += strlen(md->mask)+1;
if (md->reason)
mem += strlen(md->reason)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_SQLINE_MEM,
count, (mem+512) / 1024);
count = mem = 0;
for (md = first_maskdata(MD_SZLINE); md; md = next_maskdata(MD_SZLINE)) {
count++;
mem += sizeof(*md);
if (md->mask)
mem += strlen(md->mask)+1;
if (md->reason)
mem += strlen(md->reason)+1;
}
notice_lang(s_OperServ, user, OPER_STATS_ALL_SZLINE_MEM,
count, (mem+512) / 1024);
return 0;
}
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
const int32 module_version = MODULE_VERSION_CODE;
ConfigDirective module_config[] = {
{ NULL }
};
/*************************************************************************/
static int do_load_module(Module *mod, const char *modname)
{
if (strcmp(modname, "operserv/main") == 0) {
module_operserv = mod;
if (!add_callback(mod, "STATS ALL", do_stats_all))
module_log("Unable to register OperServ STATS ALL callback");
} else if (strcmp(modname, "operserv/akill") == 0) {
module_operserv_akill = mod;
} else if (strcmp(modname, "operserv/news") == 0) {
module_operserv_news = mod;
} else if (strcmp(modname, "operserv/sline") == 0) {
module_operserv_sline = mod;
} else if (strcmp(modname, "nickserv/main") == 0) {
module_nickserv = mod;
} else if (strcmp(modname, "chanserv/main") == 0) {
module_chanserv = mod;
} else if (strcmp(modname, "statserv/main") == 0) {
module_statserv = mod;
}
return 0;
}
/*************************************************************************/
static int do_unload_module(Module *mod)
{
if (mod == module_operserv) {
close_operserv_db(NULL);
module_operserv = NULL;
} else if (mod == module_operserv_akill) {
close_akill_db(NULL);
module_operserv_akill = NULL;
} else if (mod == module_operserv_news) {
close_news_db(NULL);
module_operserv_news = NULL;
} else if (mod == module_operserv_news) {
close_sline_db(NULL);
module_operserv_sline = NULL;
} else if (mod == module_nickserv) {
close_nick_db(NULL);
module_nickserv = NULL;
} else if (mod == module_chanserv) {
close_channel_db(NULL);
module_chanserv = NULL;
} else if (mod == module_statserv) {
close_statserv_db(NULL);
module_statserv = NULL;
}
return 0;
}
/*************************************************************************/
int init_module(Module *module_)
{
module = module_;
/* Ensure a protocol module has already been loaded--this is a bit of a
* kludge to keep kludges out of the *Serv modules, since those modules
* depend on this one */
if (protocol_features & PF_UNSET) {
module_log("No protocol module has been loaded! Protocol modules"
" must be loaded before any other modules.");
return 0;
}
if (!add_callback(NULL, "load module", do_load_module)
|| !add_callback(NULL, "unload module", do_unload_module)
) {
module_log("Unable to add callbacks");
return 0;
}
if (!init_extsyms(MODULE_NAME))
return 0;
init_fileutil(module);
return 1;
}
/*************************************************************************/
int exit_module(int shutdown)
{
if (!shutdown) {
/* Do not allow removal */
return 0;
}
exit_extsyms();
remove_callback(NULL, "unload module", do_unload_module);
remove_callback(NULL, "load module", do_load_module);
return 1;
}
/*************************************************************************/
/*************************************************************************/
EXPORT_FUNC(add_nickinfo)
EXPORT_FUNC(del_nickinfo)
EXPORT_FUNC(get_nickinfo)
EXPORT_FUNC(put_nickinfo)
EXPORT_FUNC(first_nickinfo)
EXPORT_FUNC(next_nickinfo)
EXPORT_FUNC(add_nickgroupinfo)
EXPORT_FUNC(del_nickgroupinfo)
EXPORT_FUNC(get_nickgroupinfo)
EXPORT_FUNC(put_nickgroupinfo)
EXPORT_FUNC(first_nickgroupinfo)
EXPORT_FUNC(next_nickgroupinfo)
EXPORT_FUNC(open_nick_db)
EXPORT_FUNC(sync_nick_db)
EXPORT_FUNC(close_nick_db)
EXPORT_FUNC(add_channelinfo)
EXPORT_FUNC(del_channelinfo)
EXPORT_FUNC(get_channelinfo)
EXPORT_FUNC(put_channelinfo)
EXPORT_FUNC(first_channelinfo)
EXPORT_FUNC(next_channelinfo)
EXPORT_FUNC(open_channel_db)
EXPORT_FUNC(sync_channel_db)
EXPORT_FUNC(close_channel_db)
EXPORT_FUNC(get_operserv_data)
EXPORT_FUNC(put_operserv_data)
EXPORT_FUNC(open_operserv_db)
EXPORT_FUNC(sync_operserv_db)
EXPORT_FUNC(close_operserv_db)
EXPORT_FUNC(add_news)
EXPORT_FUNC(del_news)
EXPORT_FUNC(get_news)
EXPORT_FUNC(put_news)
EXPORT_FUNC(first_news)
EXPORT_FUNC(next_news)
EXPORT_FUNC(open_news_db)
EXPORT_FUNC(sync_news_db)
EXPORT_FUNC(close_news_db)
EXPORT_FUNC(add_maskdata)
EXPORT_FUNC(del_maskdata)
EXPORT_FUNC(get_maskdata)
EXPORT_FUNC(put_maskdata)
EXPORT_FUNC(first_maskdata)
EXPORT_FUNC(next_maskdata)
EXPORT_FUNC(open_akill_db)
EXPORT_FUNC(sync_akill_db)
EXPORT_FUNC(close_akill_db)
EXPORT_FUNC(get_exception_by_num)
EXPORT_FUNC(move_exception)
EXPORT_FUNC(open_exception_db)
EXPORT_FUNC(sync_exception_db)
EXPORT_FUNC(close_exception_db)
EXPORT_FUNC(open_sline_db)
EXPORT_FUNC(sync_sline_db)
EXPORT_FUNC(close_sline_db)
syntax highlighted by Code2HTML, v. 0.9.1