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