/* 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