PHP Kod: Kodu kopyalamak için üzerine çift tıklayın!
/* Routines to load/save Services databases in the obsolete format used
* by version 4.x (and 5.0).
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch[MENTION=117372]ach[/MENTION]urch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#include "services.h"
#include "modules.h"
#include "conffile.h"
#include "databases.h"
#include "hash.h"
#include "language.h"
#include "encrypt.h"
#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/sline.h"
#include "modules/statserv/statserv.h"
#include "extsyms.h"
/* Avoid symbol clashes with database/standard when using static modules */
#define check_file_version version4_check_file_version
#define get_file_version version4_get_file_version
#define write_file_version version4_write_file_version
#define open_db version4_open_db
#define restore_db version4_restore_db
#define close_db version4_close_db
#define read_int8 version4_read_int8
#define read_uint8 version4_read_uint8
#define write_int8 version4_write_int8
#define read_int16 version4_read_int16
#define read_uint16 version4_read_uint16
#define write_int16 version4_write_int16
#define read_int32 version4_read_int32
#define read_uint32 version4_read_uint32
#define write_int32 version4_write_int32
#define read_time version4_read_time
#define write_time version4_write_time
#define read_ptr version4_read_ptr
#define write_ptr version4_write_ptr
#define read_string version4_read_string
#define write_string version4_write_string
#include "fileutil.c"
#define SAFE(x) do { if ((x) < 0) goto fail; } while (0)
/*************************************************************************/
#define FILE_VERSION 11 /* Must remain constant */
#define LOCAL_VERSION 100 /* For extensions to database files */
#define FIRST_VERSION_51 100 /* First extension version in 5.1 */
#define LOCAL_VERSION_50 27 /* Version we show to 5.0 */
/* LOCAL_VERSION change history:
* [5.1]
* 100: First version
* [5.0]
* 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 handling servadmins/servopers in 4.5.x databases: */
#define MAX_SERVOPERS 256
#define MAX_SERVADMINS 256
static nickname_t services_admins[MAX_SERVADMINS];
static nickname_t services_opers[MAX_SERVOPERS];
static int services_admins_count = 0, services_opers_count = 0;
/* Database file names (loaded from modules.conf): */
static char *NickDBName;
static char *ChanDBName;
static char *OperDBName;
static char *NewsDBName;
static char *AutokillDBName;
static char *ExceptionDBName;
static char *SlineDBName;
static char *StatDBName;
/*************************************************************************/
/**************************** Table load/save ****************************/
/*************************************************************************/
/* Forward declarations of individual load/save routines */
static int load_nick_table(DBTable *nick_table, DBTable *nickgroup_table);
static int save_nick_table(DBTable *nick_table, DBTable *nickgroup_table);
static int load_chan_table(DBTable *table);
static int save_chan_table(DBTable *table);
static int load_oper_table(DBTable *table);
static int save_oper_table(DBTable *table);
static int load_news_table(DBTable *table);
static int save_news_table(DBTable *table);
static int load_akill_table(DBTable *akill_table, DBTable *exclude_table);
static int save_akill_table(DBTable *akill_table, DBTable *exclude_table);
static int load_exception_table(DBTable *table);
static int save_exception_table(DBTable *table);
static int load_sline_table(DBTable *sgline_table, DBTable *sqline_table,
DBTable *szline_table);
static int save_sline_table(DBTable *sgline_table, DBTable *sqline_table,
DBTable *szline_table);
static int load_stat_servers_table(DBTable *table);
static int save_stat_servers_table(DBTable *table);
static int load_generic_table(DBTable *table);
static int save_generic_table(DBTable *table);
/*************************************************************************/
static int version4_load_table(DBTable *table)
{
int retval = 1;
/* In order to load nicknames properly, we need access to both nick
* and nickgroup tables, so we save the value of each table when it's
* passed in, and only call load_nick_table() when we have both values
* (after which we clear them for the next time around). */
static DBTable *nick_table = NULL, *nickgroup_table = NULL;
/* Likewise for the autokill/exclude and S-line tables. */
static DBTable *akill_table = NULL, *exclude_table = NULL,
*sgline_table = NULL, *sqline_table = NULL,
*szline_table = NULL;
/* Most load routines have to go through the data multiple times to
* read it in, due to the workarounds used to maintain backwards
* compatibility. The expiration timestamps may not be correct during
* this time, so disable expiration while the load is in progress. */
int saved_noexpire = noexpire;
noexpire = 1;
if (strcmp(table->name, "nick") == 0) {
nick_table = table;
} else if (strcmp(table->name, "nickgroup") == 0) {
nickgroup_table = table;
} else if (strcmp(table->name, "nick-access") == 0
|| strcmp(table->name, "nick-autojoin") == 0
|| strcmp(table->name, "memo") == 0
|| strcmp(table->name, "memo-ignore") == 0) {
/* ignore */
} else if (strcmp(table->name, "chan") == 0) {
retval = load_chan_table(table);
} else if (strcmp(table->name, "chan-access") == 0
|| strcmp(table->name, "chan-akick") == 0) {
/* ignore */
} else if (strcmp(table->name, "oper") == 0) {
retval = load_oper_table(table);
} else if (strcmp(table->name, "news") == 0) {
retval = load_news_table(table);
} else if (strcmp(table->name, "akill") == 0) {
akill_table = table;
} else if (strcmp(table->name, "exclude") == 0) {
exclude_table = table;
} else if (strcmp(table->name, "exception") == 0) {
retval = load_exception_table(table);
} else if (strcmp(table->name, "sgline") == 0) {
sgline_table = table;
} else if (strcmp(table->name, "sqline") == 0) {
sqline_table = table;
} else if (strcmp(table->name, "szline") == 0) {
szline_table = table;
} else if (strcmp(table->name, "stat-servers") == 0) {
retval = load_stat_servers_table(table);
} else {
retval = load_generic_table(table);
}
if (nick_table && nickgroup_table) {
/* Got both tables, run the load routine and clear the variables */
retval = load_nick_table(nick_table, nickgroup_table);
nick_table = nickgroup_table = NULL;
}
if (akill_table && exclude_table) {
retval = load_akill_table(akill_table, exclude_table);
akill_table = exclude_table = NULL;
}
if (sgline_table && sqline_table && szline_table) {
retval = load_sline_table(sgline_table, sqline_table, szline_table);
sgline_table = sqline_table = szline_table = NULL;
}
noexpire = saved_noexpire;
if (retval && table->postload && !(*table->postload)()) {
module_log_perror("Table %s postload routine failed", table->name);
retval = 0;
}
return retval;
}
/*************************************************************************/
static int version4_save_table(DBTable *table)
{
/* See version4_load_table() for why we need these */
static DBTable *nick_table = NULL, *nickgroup_table = NULL,
*akill_table = NULL, *exclude_table = NULL,
*sgline_table = NULL, *sqline_table = NULL,
*szline_table = NULL;
if (strcmp(table->name, "nick") == 0) {
nick_table = table;
} else if (strcmp(table->name, "nickgroup") == 0) {
nickgroup_table = table;
} else if (strcmp(table->name, "nick-access") == 0
|| strcmp(table->name, "nick-autojoin") == 0
|| strcmp(table->name, "memo") == 0
|| strcmp(table->name, "memo-ignore") == 0) {
/* ignore */
} else if (strcmp(table->name, "chan") == 0) {
return save_chan_table(table);
} else if (strcmp(table->name, "chan-access") == 0
|| strcmp(table->name, "chan-akick") == 0) {
/* ignore */
} else if (strcmp(table->name, "oper") == 0) {
return save_oper_table(table);
} else if (strcmp(table->name, "news") == 0) {
return save_news_table(table);
} else if (strcmp(table->name, "akill") == 0) {
akill_table = table;
} else if (strcmp(table->name, "exclude") == 0) {
exclude_table = table;
} else if (strcmp(table->name, "exception") == 0) {
return save_exception_table(table);
} else if (strcmp(table->name, "sgline") == 0) {
sgline_table = table;
} else if (strcmp(table->name, "sqline") == 0) {
sqline_table = table;
} else if (strcmp(table->name, "szline") == 0) {
szline_table = table;
} else if (strcmp(table->name, "stat-servers") == 0) {
return save_stat_servers_table(table);
} else {
return save_generic_table(table);
}
if (nick_table && nickgroup_table) {
int retval = save_nick_table(nick_table, nickgroup_table);
nick_table = nickgroup_table = NULL;
return retval;
}
if (akill_table && exclude_table) {
int retval = save_akill_table(akill_table, exclude_table);
akill_table = exclude_table = NULL;
return retval;
}
if (sgline_table && sqline_table && szline_table) {
int retval =
save_sline_table(sgline_table, sqline_table, szline_table);
sgline_table = sqline_table = szline_table = NULL;
return retval;
}
return 1;
}
/*************************************************************************/
/*************************************************************************/
/* 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;
}
/*************************************************************************/
/* Read a MaskData category from a file. */
static int read_maskdata(DBTable *table, uint8 type, const char *dbname,
dbFILE *f)
{
int32 ver;
MaskData *md;
int16 count;
int i;
#if CLEAN_COMPILE
count = 0;
#endif
SAFE(read_int16(&count, f));
for (i = 0; i < count; i++) {
int32 t---2;
md = table->newrec();
SAFE(read_string(&md->mask, f));
if (type == MD_EXCEPTION) {
SAFE(read_int16(&md->limit, f));
SAFE(read_buffer(md->who, f));
SAFE(read_string(&md->reason, f));
} else {
SAFE(read_string(&md->reason, f));
SAFE(read_buffer(md->who, f));
}
SAFE(read_int32(&t---2, f));
md->time = t---2;
SAFE(read_int32(&t---2, f));
md->expires = t---2;
md->num = i+1;
table->insert(md);
}
if (read_int32(&ver, f) == 0) {
if (ver <= FILE_VERSION || ver > LOCAL_VERSION) {
module_log("Invalid extension data version in %s", dbname);
return 0;
}
for (i = 0; i < count; i++) {
/* O(n^2), but we can't help it if we want to be robust */
for (md = table->first(); md; md = table->next()) {
if (md->num == i+1)
break;
}
if (md) {
SAFE(read_time(&md->time, f));
SAFE(read_time(&md->expires, f));
SAFE(read_time(&md->lastused, f));
}
}
}
return 1;
fail:
close_db(f);
module_log("Read error on %s", dbname);
return 0;
}
/*************************************************************************/
/* Write a MaskData category to a file. */
static int write_maskdata(DBTable *table, uint8 type, const char *dbname,
dbFILE *f)
{
static time_t lastwarn[256];
MaskData *md;
int count;
int save_noexpire = noexpire;
count = 0;
for (md = table->first(); md; md = table->next())
count++;
write_int16(count, f);
/* Disable expiration for the remainder of this function. This is a
* kludge to ensure that nothing expires while we're writing it out. */
noexpire = 1;
for (md = table->first(); md; md = table->next()) {
SAFE(write_string(md->mask, f));
if (type == MD_EXCEPTION) {
SAFE(write_int16(md->limit, f));
SAFE(write_buffer(md->who, f));
SAFE(write_string(md->reason, f));
} else {
SAFE(write_string(md->reason, f));
SAFE(write_buffer(md->who, f));
}
SAFE(write_int32(md->time, f));
SAFE(write_int32(md->expires, f));
}
SAFE(write_int32(LOCAL_VERSION_50, f));
for (md = table->first(); md; md = table->next()) {
SAFE(write_time(md->time, f));
SAFE(write_time(md->expires, f));
SAFE(write_time(md->lastused, f));
}
noexpire = save_noexpire;
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);
}
noexpire = save_noexpire;
return 0;
}
/*************************************************************************/
/********************** NickServ database handling ***********************/
/*************************************************************************/
#define NGI_TEMP_ID 0xFFFFFFFFU /* until we know the real group */
static NickInfo *load_one_nick(DBTable *nick_table, DBTable *nickgroup_table,
dbFILE *f, int32 ver)
{
NickInfo *ni;
NickGroupInfo *ngi;
int16 tmp16;
int32 t---2;
int i;
char passbuf[PASSMAX];
char *url, *email;
ni = nick_table->newrec();
SAFE(read_buffer(ni->nick, f));
module_log_debug(2, "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(&t---2, f));
ni->time_registered = t---2;
SAFE(read_int32(&t---2, f));
ni->last_seen = t---2;
SAFE(read_int16(&ni->status, f));
ni->status &= NS_PERMANENT;
/* 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 before version 5.0, 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 = nickgroup_table->newrec();
ngi->id = NGI_TEMP_ID;
ARRAY_EXTEND(ngi->nicks);
strbcpy(ngi->nicks[0], ni->nick);
init_password(&ngi->pass);
encrypt_password("a", 1, &ngi->pass); /* get encryption type */
memcpy(ngi->pass.password, 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;
ngi->flags |= NF_NOGROUP;
if (ver >= 9) {
void *tmpptr;
SAFE(read_ptr(&tmpptr, f));
if (tmpptr)
ngi->flags |= NF_SUSPENDED;
else
ngi->flags &= ~NF_SUSPENDED;
} else if (ver == 8 && (ngi->flags & 0x10000000)) {
/* In version 8, 0x10000000 was NI_SUSPENDED */
ngi->flags |= NF_SUSPENDED;
} else {
ngi->flags &= ~NF_SUSPENDED; /* just in case */
}
if (ngi->flags & NF_SUSPENDED) {
SAFE(read_buffer(ngi->suspend_who, f));
SAFE(read_string(&ngi->suspend_reason, f));
SAFE(read_int32(&t---2, f));
ngi->suspend_time = t---2;
SAFE(read_int32(&t---2, f));
ngi->suspend_expires = t---2;
}
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(&t---2, f));
ngi->memos.memos[i].time = t---2;
if (ngi->memos.memos[i].flags & MF_UNREAD)
ngi->memos.memos[i].firstread = 0;
else
ngi->memos.memos[i].firstread = t---2;
SAFE(read_buffer(ngi->memos.memos[i].sender, f));
ngi->memos.memos[i].channel = NULL;
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);
ngi->flags &= ~NF_NOGROUP;
}
/* 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) {
nickgroup_table->insert(ngi);
} else {
nickgroup_table->freerec(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(DBTable *nick_table, DBTable *nickgroup_table,
dbFILE *f, int32 ver)
{
char *nick;
NickInfo *ni = NULL;
NickInfo dummy_ni; /* for nonexistent nicks */
SAFE(read_string(&nick, f));
if (!nick)
goto fail;
module_log_debug(2, "loading nick extension %s", nick);
if (!(ni = get_nickinfo(nick))) {
module_log("Extension data found for nonexistent 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);
else
put_nickinfo(ni);
return 1;
fail:
module_log("Read error on %s", f->filename);
if (ni != &dummy_ni)
put_nickinfo(ni);
return 0;
}
/*************************************************************************/
/* Load extension data for a nick group. */
static int load_one_nickgroup_ext(DBTable *table, dbFILE *f, int32 ver)
{
uint32 group;
NickGroupInfo *ngi = NULL;
NickGroupInfo dummy_ngi; /* for nonexistent nick groups */
int i;
SAFE(read_uint32(&group, f));
module_log_debug(2, "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 nonexistent 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);
} else {
put_nickgroupinfo(ngi);
}
return 1;
fail:
module_log("Read error on %s", f->filename);
if (ngi != &dummy_ngi)
put_nickgroupinfo(ngi);
return 0;
}
/*************************************************************************/
/* Load version 5.1 extension data for a nick group. */
static int load_one_nickgroup_ext51(DBTable *table, dbFILE *f, int32 ver)
{
uint16 tmp16;
uint32 group;
NickGroupInfo *ngi = NULL;
NickGroupInfo dummy_ngi; /* for nonexistent nick groups */
int i;
SAFE(read_uint32(&group, f));
module_log_debug(2, "loading nickgroup extension %u", group);
if (!group) {
goto fail;
} else if (!(ngi = get_nickgroupinfo(group))) {
module_log("5.1 extension data found for nonexistent nick group %u",
group);
ngi = &dummy_ngi;
memset(ngi, 0, sizeof(*ngi));
init_password(&ngi->pass);
}
SAFE(read_string(&ngi->last_email, f));
SAFE(read_uint16(&tmp16, f));
if (ngi != &dummy_ngi && tmp16 != ngi->memos.memos_count) {
module_log("Warning: memo count mismatch on nickgroup %u"
" (main data: %d, ext51 data: %d)", ngi->id,
ngi->memos.memos_count, tmp16);
}
for (i = 0; i < tmp16; i++) {
time_t t;
char *s;
SAFE(read_time(&t, f));
SAFE(read_string(&s, f));
if (ngi != &dummy_ngi && i < ngi->memos.memos_count) {
ngi->memos.memos[i].firstread = t;
ngi->memos.memos[i].channel = s;
} else {
free(s);
}
}
free((char *)ngi->pass.cipher);
SAFE(read_string((char **)&ngi->pass.cipher, 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);
clear_password(&ngi->pass);
} else {
put_nickgroupinfo(ngi);
}
return 1;
fail:
module_log("Read error on %s", f->filename);
if (ngi != &dummy_ngi)
put_nickgroupinfo(ngi);
return 0;
}
/*************************************************************************/
static int nick_memomax_callback(Module *mod, const char *name)
{
NickGroupInfo *ngi;
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;
}
/*************************************************************************/
static int load_nick_table(DBTable *nick_table, DBTable *nickgroup_table)
{
dbFILE *f;
int32 ver;
int i, c;
NickInfo *ni;
NickGroupInfo *ngi;
int failed = 0, need_memomax_check = 1;
/* Open database. */
if (!(f = my_open_db_r(NickDBName, &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) {
module_log("Invalid format in nick.db");
failed = 1;
}
ni = load_one_nick(nick_table, nickgroup_table, f, ver);
if (ni) {
nick_table->insert(ni);
} else {
failed = 1;
}
}
}
/* Assign nickgroup IDs to groups that don't have them (e.g. from a
* 4.5 database) */
for (ngi = nickgroup_table->first(); ngi; ngi = nickgroup_table->next()) {
if (ngi->flags & NF_NOGROUP) {
#define NEWNICKGROUP_TRIES 1000
int tries;
for (tries = 0; tries < NEWNICKGROUP_TRIES; tries++) {
uint32 id = rand() + rand();
if (id == 0 || id == NGI_TEMP_ID)
id = 1;
if (!get_nickgroupinfo(id)) {
ngi->id = id;
break;
}
}
if (tries >= NEWNICKGROUP_TRIES) {
module_log("load_nick_table(): unable to assign new ID to"
" nickgroup for nick %s--dropping", ngi->nicks[0]);
ARRAY_FOREACH (i, ngi->nicks) {
if ((ni = get_nickinfo(ngi->nicks[i])) != NULL)
del_nickinfo(ni);
}
del_nickgroupinfo(ngi);
}
}
}
/* 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 = nick_table->first(); ni; ni = nick_table->next()) {
if (ni->last_realmask) {
char *s = ni->last_realmask;
ni->last_realmask = (char *)get_nickinfo(s);
free(s);
}
}
for (ni = nick_table->first(); ni; ni = nick_table->next()) {
if (ni->last_realmask) {
NickInfo *root = (NickInfo *)ni->last_realmask;
NickGroupInfo *ngi;
while (root->last_realmask)
root = (NickInfo *)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);
strbcpy(ngi->nicks[ngi->nicks_count-1], ni->nick);
put_nickgroupinfo(ngi);
}
}
if (!ni->nickgroup && !(ni->status & NS_VERBOTEN)) {
module_log("Nick %s has no settings (linked to missing nick?),"
" deleting", ni->nick);
if (ni->last_realmask)
put_nickinfo((NickInfo *)ni->last_realmask);
ni->last_realmask = NULL; /* Don't free someone else's NickInfo */
del_nickinfo(ni);
}
}
/* Copy all last_usermask fields to last_realmask. */
for (ni = nick_table->first(); ni; ni = nick_table->next()) {
if (ni->last_realmask)
put_nickinfo((NickInfo *)ni->last_realmask);
ni->last_realmask = sstrdup(ni->last_usermask);
}
/* Load extension data if present. */
ver = 0;
if (read_int32(&ver, f) == 0) {
if (ver <= FILE_VERSION || ver > LOCAL_VERSION_50) {
module_log("Invalid extension data version in nick.db");
failed = 1;
} else {
while (!failed && (c = getc_db(f)) != 0) {
if (c != 1) {
module_log("Invalid format in nick.db extension data");
failed = 1;
} else if (!load_one_nick_ext(nick_table, nickgroup_table,
f, ver)) {
failed = 1;
}
}
while (!failed && (c = getc_db(f)) != 0) {
if (c != 1) {
module_log("Invalid format in nick.db extension data");
failed = 1;
} else if (!load_one_nickgroup_ext(nickgroup_table, f, ver)) {
failed = 1;
}
}
}
need_memomax_check = 0;
}
if (read_int32(&ver, f) == 0) {
if (ver < FIRST_VERSION_51 || ver > LOCAL_VERSION) {
module_log("Invalid extension (5.1) data version in nick.db");
failed = 1;
} else {
while (!failed && (c = getc_db(f)) != 0) {
if (c != 1) {
module_log("Invalid format in nick.db extension (5.1)"
" data");
failed = 1;
} else if (!load_one_nickgroup_ext51(nickgroup_table, f, ver)){
failed = 1;
}
}
}
}
if (!failed && 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;
put_nickgroupinfo(ngi);
}
put_nickinfo(ni);
}
ARRAY_FOREACH (i, services_opers) {
ni = get_nickinfo(services_opers[i]);
if (ni != NULL && (ngi = get_ngi(ni)) != NULL) {
ngi->os_priv = NP_SERVOPER;
put_nickgroupinfo(ngi);
}
put_nickinfo(ni);
}
}
/* Add the memomax check callback if needed. */
if (!failed && need_memomax_check)
add_callback(NULL, "load module", nick_memomax_callback);
/* Close database. */
close_db(f);
/* All done! */
return !failed || forceload;
}
/*************************************************************************/
static int save_nick_table(DBTable *nick_table, DBTable *nickgroup_table)
{
dbFILE *f;
int i;
NickInfo *ni;
NickGroupInfo *ngi;
static NickGroupInfo forbidden_ngi; /* dummy for forbidden nicks */
static time_t lastwarn = 0;
if (!(f = open_db(NickDBName, "w", 11)))
return 0;
for (ni = nick_table->first(); ni; ni = nick_table->next()) {
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.password, f));
SAFE(write_string(ngi->url, f));
SAFE(write_string(ngi->email, f));
} else {
Password dummypass;
init_password(&dummypass);
if (!(ni->status & NS_VERBOTEN)) {
module_log("nick %s has no NickGroupInfo, setting password"
" to nick", ni->nick);
encrypt_password(ni->nick, strlen(ni->nick), &dummypass);
}
SAFE(write_buffer(dummypass.password, f));
clear_password(&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 t---2;
/* 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 */
t---2 = ngi->flags | 0x80000000 | (ngi->id & 0xFFFF0000)>>1;
if (t---2 & NF_KILL_IMMED)
t---2 &= ~NF_KILL_QUICK;
SAFE(write_int32(t---2, f));
SAFE(write_ptr((ngi->flags & NF_SUSPENDED) ? (void *)1 : NULL, f));
if (ngi->flags & NF_SUSPENDED) {
SAFE(write_buffer(ngi->suspend_who, f));
SAFE(write_string(ngi->suspend_reason, f));
SAFE(write_int32(ngi->suspend_time, f));
SAFE(write_int32(ngi->suspend_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));
}
put_nickgroupinfo(ngi);
} /* for (ni) */
{
/* This is an UGLY HACK but it simplifies loading. */
static char buf[256]; /* initialized to zero */
SAFE(write_buffer(buf, f));
}
services_admins_count = services_opers_count = 0;
SAFE(write_int32(LOCAL_VERSION_50, f));
for (ni = nick_table->first(); ni; ni = nick_table->next()) {
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 = nickgroup_table->first(); ngi; ngi = nickgroup_table->next()) {
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_string(ngi->yas, f));
SAFE(write_string(ngi->burc, f));
SAFE(write_string(ngi->sehir, f));
SAFE(write_string(ngi->takim, f));
SAFE(write_int16(ngi->os_priv, f));
if (ngi->os_priv >= NP_SERVADMIN) {
strbcpy(services_admins[services_admins_count++],
ngi_mainnick(ngi));
} else if (ngi->os_priv >= NP_SERVOPER) {
strbcpy(services_opers[services_opers_count++],
ngi_mainnick(ngi));
}
}
SAFE(write_int8(0, f));
SAFE(write_int32(LOCAL_VERSION, f));
for (ngi = nickgroup_table->first(); ngi; ngi = nickgroup_table->next()) {
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_string(ngi->last_email, f));
SAFE(write_int16(ngi->memos.memos_count, f));
ARRAY_FOREACH (i, ngi->memos.memos) {
SAFE(write_time(ngi->memos.memos[i].firstread, f));
SAFE(write_string(ngi->memos.memos[i].channel, f));
}
SAFE(write_string(ngi->pass.cipher, f));
}
SAFE(write_int8(0, f));
SAFE(close_db(f));
return 1;
fail:
restore_db(f);
module_log_perror("Write error on %s", NickDBName);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", NickDBName, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
/********************** ChanServ database handling ***********************/
/*************************************************************************/
/* 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(DBTable *table, dbFILE *f, int32 ver)
{
ChannelInfo *ci;
NickInfo *ni;
MemoInfo tmpmi;
int16 tmp16, lev;
int32 t---2;
int n_levels;
char *s;
int i;
ci = table->newrec();
SAFE(read_buffer(ci->name, f));
module_log_debug(2, "loading channel %s", ci->name);
SAFE(read_string(&s, f));
if (s) {
ni = get_nickinfo(s);
if (ni) {
ci->founder = ni->nickgroup;
put_nickinfo(ni);
}
free(s);
}
if (ver >= 7) {
SAFE(read_string(&s, f));
if (s) {
ni = get_nickinfo(s);
if (ni) {
ci->successor = ni->nickgroup;
put_nickinfo(ni);
}
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;
}
init_password(&ci->founderpass);
encrypt_password("a", 1, &ci->founderpass); /* get encryption type */
SAFE(read_buffer(ci->founderpass.password, 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(&t---2, f));
ci->time_registered = t---2;
SAFE(read_int32(&t---2, f));
ci->last_used = t---2;
SAFE(read_string(&ci->last_topic, f));
SAFE(read_buffer(ci->last_topic_setter, f));
SAFE(read_int32(&t---2, f));
ci->last_topic_time = t---2;
SAFE(read_int32(&ci->flags, f));
ci->flags &= CF_ALLFLAGS; /* clear any invalid flags */
if (ver >= 9) {
void *tmpptr;
SAFE(read_ptr(&tmpptr, f));
if (tmpptr) {
SAFE(read_buffer(ci->suspend_who, f));
SAFE(read_string(&ci->suspend_reason, f));
SAFE(read_int32(&t---2, f));
ci->suspend_time = t---2;
SAFE(read_int32(&t---2, f));
ci->suspend_expires = t---2;
ci->flags |= CF_SUSPENDED;
}
}
SAFE(read_int16(&tmp16, f));
n_levels = tmp16;
reset_levels(ci);
for (i = 0; i < n_levels; i++) {
SAFE(read_int16(&lev, f));
if (i < CA_SIZE)
ci->levels[i] = convert_old_level(lev);
}
SAFE(read_int16(&ci->access_count, f));
if (ci->access_count) {
ci->access = scalloc(ci->access_count, sizeof(ChanAccess));
ARRAY_FOREACH (i, ci->access) {
ci->access[i].channel = ci;
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;
put_nickinfo(ni);
}
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) {
ci->akick[i].channel = ci;
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(&tmpmi.memos_count, f));
SAFE(read_int16(&tmpmi.memomax, f));
if (tmpmi.memos_count) {
for (i = 0; i < tmpmi.memos_count; i++) {
Memo tmpmemo;
SAFE(read_uint32(&tmpmemo.number, f));
SAFE(read_int16(&tmpmemo.flags, f));
SAFE(read_int32(&t---2, f));
tmpmemo.time = t---2;
SAFE(read_buffer(tmpmemo.sender, f));
SAFE(read_string(&tmpmemo.text, f));
free(tmpmemo.text);
}
}
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(DBTable *table, dbFILE *f, int32 ver)
{
char *name;
ChannelInfo *ci = NULL;
ChannelInfo dummy_ci; /* for nonexistent channels */
int i;
int16 count, tmp16;
SAFE(read_string(&name, f));
if (!name)
goto fail;
module_log_debug(2, "loading channel extension %s", name);
if (!(ci = get_channelinfo(name))) {
module_log("Extension data found for nonexistent channel `%s'", name);
ci = &dummy_ci;
memset(ci, 0, sizeof(*ci));
}
free(name);
name = NULL;
SAFE(read_int16(&tmp16, f)); /* was memomax */
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);
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;
}
}
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);
} else {
put_channelinfo(ci);
}
return 1;
fail:
module_log("Read error on %s", f->filename);
if (ci != &dummy_ci)
put_channelinfo(ci);
return 0;
}
/*************************************************************************/
/* Load version 5.1 extension data for a channel. */
static int load_one_channel_ext51(DBTable *table, dbFILE *f, int32 ver)
{
char *name;
ChannelInfo *ci = NULL;
ChannelInfo dummy_ci; /* for nonexistent channels */
char *s;
SAFE(read_string(&name, f));
if (!name)
goto fail;
module_log_debug(2, "loading channel extension (5.1) %s", name);
if (!(ci = get_channelinfo(name))) {
module_log("5.1 extension data found for nonexistent channel `%s'",
name);
ci = &dummy_ci;
memset(ci, 0, sizeof(*ci));
init_password(&ci->founderpass);
}
free(name);
name = NULL;
SAFE(read_string(&s, f));
if (s)
ci->mlock.on = mode_string_to_flags(s, MODE_CHANNEL|MODE_NOERROR);
free(s);
SAFE(read_string(&s, f));
if (s)
ci->mlock.off = mode_string_to_flags(s, MODE_CHANNEL|MODE_NOERROR);
free(s);
free((char *)ci->founderpass.cipher);
SAFE(read_string((char **)&ci->founderpass.cipher, f));
if (ci == &dummy_ci) {
clear_password(&ci->founderpass);
} else {
put_channelinfo(ci);
}
return 1;
fail:
module_log("Read error on %s", f->filename);
if (ci != &dummy_ci)
put_channelinfo(ci);
return 0;
}
/*************************************************************************/
static int load_chan_table(DBTable *table)
{
dbFILE *f;
int32 ver;
int i, c;
ChannelInfo *ci;
int failed = 0;
/* Open database. */
if (!(f = my_open_db_r(ChanDBName, &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) {
module_log("Invalid format in %s", ChanDBName);
failed = 1;
break;
}
ci = load_one_channel(table, f, ver);
if (ci) {
if (strcmp(ci->name, "#") == 0) {
module_log("Deleting unsupported channel \"#\"");
table->freerec(ci);
} else if (!(ci->flags & CF_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);
table->freerec(ci);
} else {
NickGroupInfo *ngi = get_nickgroupinfo(ci->founder);
if (ngi) {
ARRAY_EXTEND(ngi->channels);
strbcpy(ngi->channels[ngi->channels_count-1],ci->name);
put_nickgroupinfo(ngi);
}
table->insert(ci);
}
} else {
failed = 1;
break;
}
}
}
/* Load extension data if present. */
if (!failed && read_int32(&ver, f) == 0) {
if (ver <= FILE_VERSION || ver > LOCAL_VERSION_50) {
module_log("Invalid extension data version in %s", ChanDBName);
failed = 1;
}
while (!failed && (c = getc_db(f)) != 0) {
if (c != 1) {
module_log("Invalid format in %s extension data", ChanDBName);
failed = 1;
} else {
failed = !load_one_channel_ext(table, f, ver);
}
}
}
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 = table->first(); ci; ci = table->next()) {
if (ci->levels) {
ci->levels[CA_AUTODEOP] = -1;
ci->levels[CA_NOJOIN] = -100;
}
}
}
/* Clean out all empty (mask==NULL) autokick entries */
for (ci = table->first(); ci; ci = table->next()) {
int i;
ARRAY_FOREACH (i, ci->akick) {
if (!ci->akick[i].mask) {
ARRAY_REMOVE(ci->akick, i);
i--;
}
}
}
if (!failed && read_int32(&ver, f) == 0) {
if (ver < FIRST_VERSION_51 || ver > LOCAL_VERSION) {
module_log("Invalid 5.1 extension data version in %s", ChanDBName);
failed = 1;
}
while (!failed && (c = getc_db(f)) != 0) {
if (c != 1) {
module_log("Invalid format in %s 5.1 extension data",
ChanDBName);
failed = 1;
} else {
failed = !load_one_channel_ext51(table, f, ver);
}
}
}
/* Close database and return. */
close_db(f);
return !failed || forceload;
}
/*************************************************************************/
static int save_chan_table(DBTable *table)
{
dbFILE *f;
int i;
ChannelInfo *ci;
NickGroupInfo *ngi;
static time_t lastwarn = 0;
if (!(f = open_db(ChanDBName, "w", 11)))
return 0;
for (ci = table->first(); ci; ci = table->next()) {
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));
put_nickgroupinfo(ngi);
} else {
SAFE(write_string(NULL, f));
}
if (ci->successor && (ngi = get_ngi_id(ci->successor))) {
SAFE(write_string(ngi_mainnick(ngi), f));
put_nickgroupinfo(ngi);
} else {
SAFE(write_string(NULL, f));
}
SAFE(write_buffer(ci->founderpass.password, 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->flags & CF_SUSPENDED ? (void *)1 : NULL, f));
if (ci->flags & CF_SUSPENDED) {
SAFE(write_buffer(ci->suspend_who, f));
SAFE(write_string(ci->suspend_reason, f));
SAFE(write_int32(ci->suspend_time, f));
SAFE(write_int32(ci->suspend_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 & CF_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));
put_nickgroupinfo(ngi);
}
}
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(0, f)); /* memos_count */
SAFE(write_int16(MSMaxMemos, f)); /* memomax */
SAFE(write_string(ci->entry_message, f));
} /* for (ci) */
{
/* This is an UGLY HACK but it simplifies loading. */
static char buf[256]; /* initialized to zero */
SAFE(write_buffer(buf, f));
}
SAFE(write_int32(LOCAL_VERSION_50, f));
for (ci = table->first(); ci; ci = table->next()) {
SAFE(write_int8(1, f));
SAFE(write_string(ci->name, f));
SAFE(write_int16(MEMOMAX_DEFAULT, f)); /* for 5.0's sake */
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(write_int32(LOCAL_VERSION, f));
for (ci = table->first(); ci; ci = table->next()) {
SAFE(write_string(mode_flags_to_string(ci->mlock.on,MODE_CHANNEL),f));
SAFE(write_string(mode_flags_to_string(ci->mlock.off,MODE_CHANNEL),f));
SAFE(write_string(ci->founderpass.cipher, f));
}
SAFE(write_int8(0, f));
SAFE(close_db(f));
return 1;
fail:
restore_db(f);
module_log_perror("Write error on %s", ChanDBName);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", ChanDBName, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
/********************** OperServ database handling ***********************/
/*************************************************************************/
static int load_oper_table(DBTable *table)
{
dbFILE *f;
int32 ver;
int16 i, n;
DBField *field;
void *record; /* pointer to OperServ's data structure */
int8 no_supass = 1;
Password *supass = NULL;
if (!(record = table->first())) {
module_log("BUG: record missing from OperServ table!");
return 0;
}
if (table->next()) {
module_log("BUG: too many records in OperServ table!");
return 0;
}
if (!(f = my_open_db_r(OperDBName, &ver)))
return 1;
else if (f == OPENDB_ERROR)
return 0;
services_admins_count = services_opers_count = 0;
SAFE(read_int16(&n, f));
for (i = 0; i < n; i++) {
char *s;
SAFE(read_string(&s, f));
if (s && i < MAX_SERVADMINS)
strbcpy(services_admins[services_admins_count++], s);
free(s);
}
SAFE(read_int16(&n, f));
for (i = 0; i < n; i++) {
char *s;
SAFE(read_string(&s, f));
if (s && i < MAX_SERVOPERS)
strbcpy(services_opers[services_opers_count++], s);
free(s);
}
if (ver >= 7) {
int32 t---2;
SAFE(read_int32(&t---2, f));
for (field = table->fields; field->name; field++) {
if (strcmp(field->name, "maxusercnt") == 0) {
if (field->type != DBTYPE_INT32) {
module_log("BUG: oper.maxusercnt type is not INT32!");
goto fail;
}
*((int32 *)DB_FIELDPTR(record,field)) = t---2;
break;
}
}
SAFE(read_int32(&t---2, f));
for (field = table->fields; field->name; field++) {
if (strcmp(field->name, "maxusertime") == 0) {
if (field->type != DBTYPE_TIME) {
module_log("BUG: oper.maxusertime type is not TIME!");
goto fail;
}
*((time_t *)DB_FIELDPTR(record,field)) = t---2;
break;
}
}
}
if (ver >= 9) {
SAFE(read_int8(&no_supass, f));
for (field = table->fields; field->name; field++) {
if (strcmp(field->name, "no_supass") == 0) {
if (field->type != DBTYPE_INT8) {
module_log("BUG: oper.no_supass type is not INT8!");
goto fail;
}
*((int8 *)DB_FIELDPTR(record,field)) = no_supass;
break;
}
}
if (!no_supass) {
Password tmppass;
init_password(&tmppass);
encrypt_password("a", 1, &tmppass);
SAFE(read_buffer(tmppass.password, f));
for (field = table->fields; field->name; field++) {
if (strcmp(field->name, "supass") == 0) {
if (field->type != DBTYPE_PASSWORD) {
module_log("BUG: oper.supass type is not PASSWORD!");
goto fail;
}
supass = (Password *)DB_FIELDPTR(record,field);
init_password(supass);
copy_password(supass, &tmppass);
break;
}
}
}
}
if (read_int32(&ver, f) == 0) {
if (ver < FIRST_VERSION_51 || ver > LOCAL_VERSION) {
module_log("Invalid 5.1 extension data version in %s", OperDBName);
close_db(f);
return 0;
}
SAFE(read_string((char **)&supass->cipher, f));
}
close_db(f);
return 1;
fail:
close_db(f);
module_log("Read error on %s", OperDBName);
return 0;
}
/*************************************************************************/
static int save_oper_table(DBTable *table)
{
dbFILE *f;
int16 i;
void *record;
DBField *field;
static time_t lastwarn = 0;
int8 no_supass = 1;
Password *supass = NULL;
if (!(record = table->first())) {
module_log("BUG: record missing from OperServ table!");
return 0;
}
if (table->next()) {
module_log("BUG: too many records in OperServ table!");
return 0;
}
if (!(f = open_db(OperDBName, "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));
for (field = table->fields; field->name; field++) {
if (strcmp(field->name, "maxusercnt") == 0) {
if (field->type != DBTYPE_INT32) {
module_log("BUG: oper.maxusercnt type is not INT32!");
goto fail;
}
SAFE(write_int32(*((int32 *)DB_FIELDPTR(record,field)), f));
break;
}
}
for (field = table->fields; field->name; field++) {
if (strcmp(field->name, "maxusertime") == 0) {
if (field->type != DBTYPE_TIME) {
module_log("BUG: oper.maxusertime type is not TIME!");
goto fail;
}
SAFE(write_int32((int32) *((time_t *)DB_FIELDPTR(record,field)),
f));
break;
}
}
for (field = table->fields; field->name; field++) {
if (strcmp(field->name, "no_supass") == 0) {
if (field->type != DBTYPE_INT8) {
module_log("BUG: oper.no_supass type is not INT8!");
goto fail;
}
no_supass = *((int8 *)DB_FIELDPTR(record,field));
SAFE(write_int8(no_supass, f));
break;
}
}
if (!no_supass) {
for (field = table->fields; field->name; field++) {
if (strcmp(field->name, "supass") == 0) {
if (field->type != DBTYPE_PASSWORD) {
module_log("BUG: oper.supass type is not PASSWORD!");
goto fail;
}
supass = (Password *)DB_FIELDPTR(record,field);
SAFE(write_buffer(supass->password, f));
break;
}
}
}
SAFE(write_int32(LOCAL_VERSION, f));
if (!no_supass)
SAFE(write_string(supass->cipher, f));
SAFE(close_db(f));
return 1;
fail:
restore_db(f);
module_log_perror("Write error on %s", OperDBName);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", OperDBName, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
/************************ News database handling *************************/
/*************************************************************************/
static int load_news_table(DBTable *table)
{
dbFILE *f;
int32 ver;
int i;
int16 count;
if (!(f = my_open_db_r(NewsDBName, &ver)))
return 1;
else if (f == OPENDB_ERROR)
return 0;
#if CLEAN_COMPILE
count = 0;
#endif
SAFE(read_int16(&count, f));
for (i = 0; i < count; i++) {
int32 t---2;
NewsItem *news = table->newrec();
SAFE(read_int16(&news->type, f));
SAFE(read_int32(&news->num, f));
SAFE(read_string(&news->text, f));
SAFE(read_buffer(news->who, f));
SAFE(read_int32(&t---2, f));
news->time = t---2;
table->insert(news);
}
close_db(f);
return 1;
fail:
close_db(f);
module_log("Read error on %s", NewsDBName);
return 0;
}
/*************************************************************************/
static int save_news_table(DBTable *table)
{
dbFILE *f;
int count, i;
NewsItem *news;
static time_t lastwarn = 0;
if (!(f = open_db(NewsDBName, "w", 11)))
return 0;
count = 0;
for (news = table->first(); news; news = table->next())
count++;
if (count > 32767)
count = 32767; // avoid overflow
write_int16((int16)count, f);
i = 0;
for (news = table->first(), i = 0; i < count; news = table->next(), i++) {
if (!news) {
module_log("BUG: news item count changed while saving!");
wallops(NULL, "Error saving %s! Please check log file.",
NewsDBName);
restore_db(f);
return 0;
}
SAFE(write_int16(news->type, f));
SAFE(write_int32(news->num, f));
SAFE(write_string(news->text, f));
SAFE(write_buffer(news->who, f));
SAFE(write_int32(news->time, f));
}
SAFE(close_db(f));
return 1;
fail:
restore_db(f);
module_log_perror("Write error on %s", NewsDBName);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", NewsDBName, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
/********************** Autokill database handling ***********************/
/*************************************************************************/
static int load_akill_table(DBTable *akill_table, DBTable *exclude_table)
{
dbFILE *f;
int32 ver;
if (!(f = my_open_db_r(AutokillDBName, &ver)))
return 1;
else if (f == OPENDB_ERROR)
return 0;
if (!read_maskdata(akill_table, MD_AKILL, AutokillDBName, f))
return 0;
if (getc_db(f) == 1) {
if (!read_maskdata(exclude_table, MD_EXCLUDE, AutokillDBName, f))
return 0;
}
close_db(f);
return 1;
}
/*************************************************************************/
static int save_akill_table(DBTable *akill_table, DBTable *exclude_table)
{
dbFILE *f;
static time_t lastwarn = 0;
if (!(f = open_db(AutokillDBName, "w", 11)))
return 0;
if (!write_maskdata(akill_table, MD_AKILL, AutokillDBName, f))
return 0;
SAFE(write_int8(1, f));
if (!write_maskdata(exclude_table, MD_EXCLUDE, AutokillDBName, f))
return 0;
SAFE(close_db(f));
return 1;
fail:
restore_db(f);
module_log_perror("Write error on %s", AutokillDBName);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", AutokillDBName,
strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
/********************** Exception database handling **********************/
/*************************************************************************/
static int load_exception_table(DBTable *table)
{
dbFILE *f;
int32 ver;
if (!(f = my_open_db_r(ExceptionDBName, &ver)))
return 1;
else if (f == OPENDB_ERROR)
return 0;
if (!read_maskdata(table, MD_EXCEPTION, ExceptionDBName, f))
return 0;
close_db(f);
return 1;
}
/*************************************************************************/
static int save_exception_table(DBTable *table)
{
dbFILE *f;
static time_t lastwarn = 0;
if (!(f = open_db(ExceptionDBName, "w", 11)))
return 0;
if (!write_maskdata(table, MD_EXCEPTION, ExceptionDBName, f))
return 0;
SAFE(close_db(f));
return 1;
fail:
restore_db(f);
module_log_perror("Write error on %s", ExceptionDBName);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", ExceptionDBName,
strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
/*********************** S-line database handling ************************/
/*************************************************************************/
static int load_sline_table(DBTable *sgline_table, DBTable *sqline_table,
DBTable *szline_table)
{
dbFILE *f;
int32 ver;
if (!(f = my_open_db_r(SlineDBName, &ver)))
return 1;
else if (f == OPENDB_ERROR)
return 0;
if (!read_maskdata(sgline_table, MD_SGLINE, SlineDBName, f))
return 0;
if (!read_maskdata(sqline_table, MD_SQLINE, SlineDBName, f))
return 0;
if (!read_maskdata(szline_table, MD_SZLINE, SlineDBName, f))
return 0;
close_db(f);
return 1;
}
/*************************************************************************/
static int save_sline_table(DBTable *sgline_table, DBTable *sqline_table,
DBTable *szline_table)
{
dbFILE *f;
static time_t lastwarn = 0;
if (!(f = open_db(SlineDBName, "w", 11)))
return 0;
if (!write_maskdata(sgline_table, MD_SGLINE, SlineDBName, f))
return 0;
if (!write_maskdata(sqline_table, MD_SQLINE, SlineDBName, f))
return 0;
if (!write_maskdata(szline_table, MD_SZLINE, SlineDBName, f))
return 0;
SAFE(close_db(f));
return 1;
fail:
restore_db(f);
module_log_perror("Write error on %s", SlineDBName);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", SlineDBName, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
/********************** StatServ database handling ***********************/
/*************************************************************************/
static ServerStats *load_one_serverstats(DBTable *table, dbFILE *f)
{
ServerStats *ss;
int32 t---2;
ss = table->newrec();
SAFE(read_string(&ss->name, f));
SAFE(read_int32(&t---2, f));
ss->t_join = t---2;
SAFE(read_int32(&t---2, 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(DBTable *table, dbFILE *f, int32 ver)
{
ServerStats *ss = NULL;
char *servername;
SAFE(read_string(&servername, f));
if (!servername)
goto fail;
ss = get_serverstats(servername);
if (!ss) {
module_log("Extension data found for nonexistent server `%s'",
servername);
free(servername);
return 0;
}
free(servername);
SAFE(read_time(&ss->t_join, f));
put_serverstats(ss);
return 1;
fail:
module_log("Read error on %s", f->filename);
put_serverstats(ss);
return 0;
}
/*************************************************************************/
static int load_stat_servers_table(DBTable *table)
{
dbFILE *f;
int32 ver, i, nservers;
int16 tmp16;
int failed = 0;
ServerStats *ss;
/* Open database. */
if (!(f = my_open_db_r(StatDBName, &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(table, f);
if (ss)
table->insert(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_50) {
module_log("Invalid extension data version in %s", StatDBName);
} else {
SAFE(read_int32(&moreservers, f));
for (i = 0; i < moreservers && !failed; i++) {
ss = load_one_serverstats(table, f);
if (ss)
table->insert(ss);
else
failed = 1;
}
nservers += moreservers;
for (i = 0; i < nservers && !failed; i++)
failed = !load_one_serverstats_ext(table, f, ver);
}
}
/* Close database and return. */
close_db(f);
return !failed || forceload;
fail:
close_db(f);
module_log("Read error on %s", StatDBName);
return 0;
}
/*************************************************************************/
static int save_stat_servers_table(DBTable *table)
{
dbFILE *f;
int32 count, realcount, i;
ServerStats *ss;
static time_t lastwarn = 0;
if (!(f = open_db(StatDBName, "w", 11)))
return 0;
realcount = 0;
for (ss = table->first(); ss; ss = table->next())
realcount++;
if (realcount > 32767) /* Well, you never know... */
count = 32767;
else
count = realcount;
SAFE(write_int16((int16)count, f));
for (ss = table->first(), i = 0; i < count; ss = table->next(), i++) {
if (!ss) {
module_log("BUG: save_stat_servers_table(): ss NULL but i <"
" count!");
wallops(NULL, "Error saving %s! Please check log file.",
StatDBName);
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_50, f));
if (realcount > count) {
SAFE(write_int32(realcount-count, f));
for (; i < realcount; ss = table->next(), i++) {
if (!ss) {
module_log("BUG: save_stat_servers_table(): ss NULL but i <"
" realcount!");
wallops(NULL, "Error saving %s! Please check log file.",
StatDBName);
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 = table->first(); ss; ss = table->next()) {
SAFE(write_string(ss->name, f));
SAFE(write_time(ss->t_join, f));
}
SAFE(close_db(f));
return 1;
fail:
restore_db(f);
module_log_perror("Write error on %s", StatDBName);
if (time(NULL) - lastwarn > WarningTimeout) {
wallops(NULL, "Write error on %s: %s", StatDBName, strerror(errno));
lastwarn = time(NULL);
}
return 0;
}
/*************************************************************************/
/*********************** Generic database handling ***********************/
/*************************************************************************/
/* Routines used for databases not part of the v4/v5.0 set */
#define INCLUDE_IN_VERSION4
#define standard_load_table load_generic_table
#define standard_save_table save_generic_table
#include "standard.c"
#undef INCLUDE_IN_VERSION4
#undef standard_load_table
#undef standard_save_table
/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/
ConfigDirective module_config[] = {
{ "AutokillDB", { { CD_STRING, CF_DIRREQ, &AutokillDBName } } },
{ "ChanServDB", { { CD_STRING, CF_DIRREQ, &ChanDBName } } },
{ "ExceptionDB", { { CD_STRING, CF_DIRREQ, &ExceptionDBName } } },
{ "NewsDB", { { CD_STRING, CF_DIRREQ, &NewsDBName } } },
{ "NickServDB", { { CD_STRING, CF_DIRREQ, &NickDBName } } },
{ "OperServDB", { { CD_STRING, CF_DIRREQ, &OperDBName } } },
{ "SlineDB", { { CD_STRING, CF_DIRREQ, &SlineDBName } } },
{ "StatServDB", { { CD_STRING, CF_DIRREQ, &StatDBName } } },
{ NULL }
};
static DBModule dbmodule_version4 = {
.load_table = version4_load_table,
.save_table = version4_save_table,
};
/*************************************************************************/
int init_module(void)
{
if (!init_extsyms(MODULE_NAME)) {
exit_module(0);
return 0;
}
if (!register_dbmodule(&dbmodule_version4)) {
module_log("Unable to register module with database core");
exit_module(0);
return 0;
}
return 1;
}
/*************************************************************************/
int exit_module(int shutdown)
{
unregister_dbmodule(&dbmodule_version4);
exit_extsyms();
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/