From 657e1deb590eaba38ea3e9a9fb353dc0e23c1a41 Mon Sep 17 00:00:00 2001 From: Adam- Date: Thu, 31 Dec 2009 01:25:10 +0000 Subject: Added in new plaintext databases. Note: This currently has no automatic backup feature. Big thanks to Phil on this for mass scale testing git-svn-id: http://anope.svn.sourceforge.net/svnroot/anope/trunk@2722 5417fbe8-f217-4b02-8779-1006273d7864 --- Changes.conf | 11 + data/example.conf | 90 +--- include/Makefile | 2 +- include/account.h | 4 +- include/config.h | 3 + include/configreader.h | 18 +- include/datafiles.h | 25 - include/extern.h | 2 - include/modules.h | 105 +++- include/pseudo.h | 1 - src/Makefile | 5 +- src/chanserv.c | 7 +- src/config.c | 34 +- src/core/db_plain.cpp | 1148 ++++++++++++++++++++++++++++++++++++++++++++ src/core/ns_forbid.c | 4 +- src/core/ns_identify.c | 3 +- src/core/os_ignore.c | 58 ++- src/core/os_modlist.c | 25 + src/core/os_news.c | 42 +- src/datafiles.c | 203 -------- src/init.c | 12 +- src/main.c | 4 +- src/modulemanager.cpp | 6 +- src/modules/hs_request.c | 150 ++---- src/modules/os_ignore_db.c | 490 ------------------- src/modules/os_info.c | 193 ++------ src/regchannel.cpp | 11 +- src/tools/db-convert.c | 110 ++--- src/tools/db-convert.h | 19 +- src/users.c | 6 +- 30 files changed, 1570 insertions(+), 1221 deletions(-) delete mode 100644 include/datafiles.h create mode 100644 src/core/db_plain.cpp delete mode 100644 src/datafiles.c delete mode 100644 src/modules/os_ignore_db.c diff --git a/Changes.conf b/Changes.conf index f084f452d..f4319af25 100644 --- a/Changes.conf +++ b/Changes.conf @@ -3,11 +3,22 @@ Anope Version 1.9.2 ** ADDED CONFIGURATION DIRECTIVES ** options:enablelogchannel added to auto turn on the logchannel on startup options:mlock added to configure the default mlock modes on new channels +options:database added for the database modules ** MODIFIED CONFIGURATION DIRECTIVES ** chanserv:modules added cs_unban ** DELETED CONFIGURATION DIRECTIVES ** +nickserv:database deleted because of new database system +nickserv:prenickdatabase deleted because of new database system +chanserv:database deleted because of the new database system +botserv:database deleted because of the new database system +hostserv:database deleted because of the new database system +operserv:database deleted because of the new database system +operserv:newsdatabase deleted because of the new database system +operserv:exceptiondatabase deleted because of the new database system +hs_request:database deleted because of the new database system +os_ignore:database deleted because of the new database system Anope Version 1.9.1 ------------------- diff --git a/data/example.conf b/data/example.conf index a0313947f..7299b5f04 100644 --- a/data/example.conf +++ b/data/example.conf @@ -238,7 +238,7 @@ networkinfo options { /* - * The encryption module to use when dealing with passwords. This determines how + * The encryption modules are used when dealing with passwords. This determines how * the passwords are stored in the databases, and does not add any security as * far as transmitting passwords over the network goes. * @@ -263,6 +263,12 @@ options */ encryption = "enc_none enc_sha1 enc_md5 enc_old" + /* + * The database modules are used for saving and loading databases for Anope. + * Currently, there is only one database module - db_plain + */ + database = "db_plain" + /* * These keys are used to initiate the random number generator. These numbers * MUST be random as you want your passcodes to be random. Don't give these @@ -701,19 +707,6 @@ nickserv */ description = "Nickname Registration Service" - /* - * The filename of the NickServ database. The path is relative to the services - * executable. If not given, defaults to "nick.db". - */ - database = "nick.db" - - /* - * The filename of the NickServ Pre-Nick database. The path is relative to the - * services executable. This directive is only required if the e-mail registration - * option is enabled. - */ - #prenickdatabase = "prenick.db" - /* * The core modules to load for NickServ. This is a space separated list that corresponds * to the base names of the modules for NickServ. This directive is optional, but highly recommended. @@ -889,12 +882,6 @@ chanserv */ description = "Channel Registration Service" - /* - * The filename of the ChanServ database. The path is relative to the services - * executable. If not given, defaults to "chan.db". - */ - database = "chan.db" - /* * The core modules to load for ChanServ. This is a space separated list that corresponds * to the base names of the modules for ChanServ. This directive is optional, but highly recommended. @@ -1072,12 +1059,6 @@ botserv */ description = "Bot Service" - /* - * The filename of the BotServ database. The path is relative to the services - * executable. If not given, defaults to "bot.db". - */ - database = "bot.db" - /* * The core modules to load for BotServ. This is a space separated list that corresponds * to the base names of the modules for BotServ. This directive is optional, but highly recommended. @@ -1171,12 +1152,6 @@ hostserv */ description = "vHost Service" - /* - * The filename of the HostServ database. The path is relative to the services - * executable. If not given, defaults to "hosts.db". - */ - database = "hosts.db" - /* * The core modules to load for HostServ. This is a space separated list that corresponds * to the base names of the modules for HostServ. This directive is optional, but highly recommended. @@ -1214,24 +1189,6 @@ operserv */ globaldescription = "Global Noticer" - /* - * The filename of the OperServ database. The path is relative to the services - * executable. If not given, defaults to "oper.db". - */ - database = "oper.db" - - /* - * The filename of OperServ's News database. The path is relative to the services - * executable. If not given, defaults to "news.db". - */ - newsdatabase = "news.db" - - /* - * The filename of OperServ's Session Exception database. The path is relative to the services - * executable. If not given, defaults to "exception.db". - */ - exceptiondatabase = "exception.db" - /* * The core modules to load for OperServ. This is a space separated list that corresponds * to the base names of the modules for OperServ. This directive is optional, but highly recommended. @@ -1529,22 +1486,6 @@ hs_request * If set, Services will send a memo to all Host Setters when a new vHost is requested. */ #memosetters = yes - - /* - * The filename of the vHost request database. The path is relative to the services - * executable. If not given, defaults to "hs_request.db". - */ - #database = "hs_request.db" -} - -ns_maxemail -{ - /* - * The limit to how many registered nicks can use the same e-mail address. If set to 0 or left - * commented, there will be no limit enforced when registering new accounts or using - * /msg NickServ SET EMAIL. - */ - #maxemails = 1 } ns_noop_convert @@ -1556,20 +1497,13 @@ ns_noop_convert #database = "autoop.db" } -os_ignore +ns_maxemail { /* - * The filename of the Services' ignore database. The path is relative to the services - * executable. If not given, defaults to "os_ignore.db". + * The limit to how many registered nicks can use the same e-mail address. If set to 0 or left + * commented, there will be no limit enforced when registering new accounts or using + * /msg NickServ SET EMAIL. */ - #database = "os_ignore.db" + #maxemails = 1 } -os_info -{ - /* - * The filename of the oper information database. The path is relative to the services - * executable. If not given, defaults to "os_info.db". - */ - #database = "os_info.db" -} diff --git a/include/Makefile b/include/Makefile index 13c5b8b2c..3d8067aa6 100644 --- a/include/Makefile +++ b/include/Makefile @@ -10,7 +10,7 @@ services.h: sysconf.h config.h extern.h extern.h: slist.h touch $@ -pseudo.h: commands.h timers.h datafiles.h slist.h +pseudo.h: commands.h timers.h slist.h touch $@ clean: diff --git a/include/account.h b/include/account.h index e20b70fe4..dc0d21c7d 100644 --- a/include/account.h +++ b/include/account.h @@ -58,8 +58,8 @@ enum NickCoreFlag NI_SUSPENDED, /* Autoop nickname in channels */ NI_AUTOOP, - /* Nick won't expire */ - NI_NOEXPIRE, + /* This nickcore is forbidden, which means the nickalias for it is aswell */ + NI_FORBIDDEN, NI_END }; diff --git a/include/config.h b/include/config.h index f9d8f4370..a5151e071 100644 --- a/include/config.h +++ b/include/config.h @@ -25,6 +25,9 @@ /* Name of configuration file (in Services directory) */ #define SERVICES_CONF "services.conf" +/* Name of anope datbase */ +#define DATABASE_FILE "anope.db" + /* Name of log file (in Services directory) */ #define LOG_FILENAME "services.log" diff --git a/include/configreader.h b/include/configreader.h index 97403b5fa..c9db6cb89 100644 --- a/include/configreader.h +++ b/include/configreader.h @@ -432,8 +432,6 @@ class ServerConfig /* Global realname */ char *desc_GlobalNoticer; - /* Name of the hostserv database */ - char *HostDBName; /* HostServ Name */ char *s_HostServ; /* HostServ realname */ @@ -443,18 +441,6 @@ class ServerConfig char *PIDFilename; /* MOTD filename */ char *MOTDFilename; - /* NickServ DB name */ - char *NickDBName; - /* DB name for nicks being registered */ - char *PreNickDBName; - /* Channel DB name */ - char *ChanDBName; - /* Botserv DB name */ - char *BotDBName; - /* OperServ db name */ - char *OperDBName; - /* News DB name */ - char *NewsDBName; /* True if its ok to not be able to save backs */ bool NoBackupOkay; @@ -695,8 +681,6 @@ class ServerConfig unsigned MaxSessionLimit; /* How long session akills should last */ time_t SessionAutoKillExpiry; - /* DB name for exceptions */ - char *ExceptionDBName; /* Reason to use for session kills */ char *SessionLimitExceeded; /* Optional second reason */ @@ -708,6 +692,8 @@ class ServerConfig std::list ModulesAutoLoad; /* Encryption modules */ std::list EncModuleList; + /* Database modules */ + std::list DBModuleList; /* HostServ Core Modules */ std::list HostServCoreModules; /* MemoServ Core Modules */ diff --git a/include/datafiles.h b/include/datafiles.h deleted file mode 100644 index fe8cb3d12..000000000 --- a/include/datafiles.h +++ /dev/null @@ -1,25 +0,0 @@ -/* Database file descriptor structure and file handling routine prototypes. - * - * (C) 2003-2009 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for furhter details. - * - * Based on the original code of Epona by Lara. - * Based on the original code of Services by Andy Church. - * - * $Id$ - * - */ - -#ifndef DATAFILES_H -#define DATAFILES_H - -#ifndef _WIN32 -#include -#define DeleteFile unlink -#endif - - - -#endif /* DATAFILES_H */ diff --git a/include/extern.h b/include/extern.h index 10c4028b4..f570c39ff 100644 --- a/include/extern.h +++ b/include/extern.h @@ -393,8 +393,6 @@ E void SetDefaultMLock(); /**** modules.c ****/ E void modules_unload_all(bool unload_proto); /* Read warnings near function source */ -E void ModuleDatabaseBackup(const char *dbname); -E void ModuleRemoveBackups(const char *dbname); /**** nickserv.c ****/ diff --git a/include/modules.h b/include/modules.h index f4d9700f3..129e2666d 100644 --- a/include/modules.h +++ b/include/modules.h @@ -186,7 +186,7 @@ enum CommandReturn const char *ano_moderr(); #endif -typedef enum { CORE,PROTOCOL,THIRD,SUPPORTED,QATESTED,ENCRYPTION } MODType; +typedef enum { CORE,PROTOCOL,THIRD,SUPPORTED,QATESTED,ENCRYPTION,DATABASE } MODType; typedef enum { MOD_OP_LOAD, MOD_OP_UNLOAD } ModuleOperation; /*************************************************************************/ @@ -561,11 +561,15 @@ class CoreExport Module */ virtual void OnPostLoadDatabases() { } - /** Called when anope saves databases. - * NOTE: This event is deprecated pending new database handling. - * XXX. + /** Called when the databases are saved + * @return EVENT_CONTINUE to let other modules continue saving, EVENT_STOP to stop + */ + virtual EventReturn OnSaveDatabase() { return EVENT_CONTINUE; } + + /** Called when the databases are loaded + * @return EVENT_CONTINUE to let other modules continue saving, EVENT_STOP to stop */ - virtual void OnSaveDatabase() MARK_DEPRECATED { } + virtual EventReturn OnLoadDatabase() { return EVENT_CONTINUE; } /** Called when anope backs up databases. * NOTE: This event is deprecated pending new database handling. @@ -684,6 +688,73 @@ class CoreExport Module */ virtual void OnDatabaseExpire() MARK_DEPRECATED { } + /** Called when the flatfile dbs are being written + * @param Write A callback to the function used to insert a line into the database + */ + virtual void OnDatabaseWrite(void (*Write)(const std::string &)) { } + + /** Called when a line is read from the database + * @param params The params from the database + * @return EVENT_CONTINUE to let other modules decide, EVENT_STOP to stop processing + */ + virtual EventReturn OnDatabaseRead(const std::vector ¶ms) { return EVENT_CONTINUE; } + + /** Called when nickcore metadata is read from the database + * @param nc The nickcore + * @param key The metadata key + * @param params The params from the database + * @return EVENT_CONTINUE to let other modules decide, EVENT_STOP to stop processing + */ + virtual EventReturn OnDatabaseReadMetadata(NickCore *nc, const std::string &key, const std::vector ¶ms) { return EVENT_CONTINUE; } + + /** Called when nickcore metadata is read from the database + * @param na The nickalias + * @param key The metadata key + * @param params The params from the database + * @return EVENT_CONTINUE to let other modules decide, EVENT_STOP to stop processing + */ + virtual EventReturn OnDatabaseReadMetadata(NickAlias *na, const std::string &key, const std::vector ¶ms) { return EVENT_CONTINUE; } + + /** Called when botinfo metadata is read from the database + * @param bi The botinfo + * @param key The metadata key + * @param params The params from the database + * @return EVENT_CONTINUE to let other modules decide, EVENT_STOP to stop processing + */ + virtual EventReturn OnDatabaseReadMetadata(BotInfo *bi, const std::string &key, const std::vector ¶ms) { return EVENT_CONTINUE; } + + /** Called when chaninfo metadata is read from the database + * @param ci The chaninfo + * @param key The metadata key + * @param params The params from the database + * @return EVENT_CONTINUE to let other modules decide, EVENT_STOP to stop processing + */ + virtual EventReturn OnDatabaseReadMetadata(ChannelInfo *ci, const std::string &key, const std::vector ¶ms) { return EVENT_CONTINUE; } + + /** Called when we are writing metadata for a nickcore + * @param WriteMetata A callback function used to insert the metadata + * @param nc The nickcore + */ + virtual void OnDatabaseWriteMetadata(void (*WriteMetadata)(const std::string &, const std::string &), NickCore *nc) { } + + /** Called when we are wrting metadata for a nickalias + * @param WriteMetata A callback function used to insert the metadata + * @param na The nick alias + */ + virtual void OnDatabaseWriteMetadata(void (*WriteMetadata)(const std::string &, const std::string &), NickAlias *na) { } + + /** Called when we are writing metadata for a botinfo + * @param WriteMetata A callback function used to insert the metadata + * @param bi The botinfo + */ + virtual void OnDatabaseWriteMetadata(void (*WriteMetadata)(const std::string &, const std::string &), BotInfo *bi) { } + + /** Called when are are writing metadata for a channelinfo + * @param WriteMetata A callback function used to insert the metadata + * @param bi The channelinfo + */ + virtual void OnDatabaseWriteMetadata(void (*WriteMetadata)(const std::string &, const std::string &), ChannelInfo *ci) { } + /** Called before services restart */ virtual void OnPreRestart() { } @@ -983,6 +1054,18 @@ class CoreExport Module */ virtual EventReturn OnUnMLock(ChannelModeName Name) { return EVENT_CONTINUE; } + /** Called after a module is loaded + * @param u The user loading the module, can be NULL + * @param m The module + */ + virtual void OnModuleLoad(User *u, Module *m) { } + + /** Called before a module is unloaded + * @param u The user, can be NULL + * @param m The module + */ + virtual void OnModuleUnload(User *u, Module *m) { } + }; @@ -1019,9 +1102,17 @@ enum Implementation /* OperServ */ I_OnOperServHelp, I_OnDefconLevel, + /* Database */ + I_OnPostLoadDatabases, I_OnSaveDatabase, I_OnLoadDatabase, I_OnBackupDatabase, + I_OnDatabaseExpire, + I_OnDatabaseWrite, I_OnDatabaseRead, I_OnDatabaseReadMetadata, I_OnDatabaseWriteMetadata, + + /* Modules */ + I_OnModuleLoad, I_OnModuleUnload, + /* Other */ - I_OnReload, I_OnPreServerConnect, I_OnNewServer, I_OnServerConnect, I_OnPreCommandRun, I_OnPreCommand, I_OnPostCommand, I_OnPostLoadDatabases, I_OnSaveDatabase, I_OnBackupDatabase, - I_OnPreDatabaseExpire, I_OnDatabaseExpire, I_OnPreRestart, I_OnRestart, I_OnPreShutdown, I_OnShutdown, I_OnSignal, + I_OnReload, I_OnPreServerConnect, I_OnNewServer, I_OnServerConnect, I_OnPreCommandRun, I_OnPreCommand, I_OnPostCommand, + I_OnPreDatabaseExpire, I_OnPreRestart, I_OnRestart, I_OnPreShutdown, I_OnShutdown, I_OnSignal, I_OnServerQuit, I_OnTopicUpdated, I_OnEncrypt, I_OnEncryptInPlace, I_OnEncryptCheckLen, I_OnDecrypt, I_OnCheckPassword, I_OnChannelModeSet, I_OnChannelModeUnset, I_OnUserModeSet, I_OnUserModeUnset, I_OnChannelModeAdd, I_OnUserModeAdd, diff --git a/include/pseudo.h b/include/pseudo.h index 65e5e81e7..d356aa953 100644 --- a/include/pseudo.h +++ b/include/pseudo.h @@ -15,5 +15,4 @@ #include "commands.h" #include "language.h" #include "timers.h" -#include "datafiles.h" #include "slist.h" diff --git a/src/Makefile b/src/Makefile index 8257d8cdf..90705ce29 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ OBJS = actions.o base64.o bots.o botserv.o channels.o chanserv.o command.o commands.o compat.o \ - config.o datafiles.o encrypt.o hashcomp.o hostserv.o init.o ircd.o language.o log.o mail.o main.o \ + config.o encrypt.o hashcomp.o hostserv.o init.o ircd.o language.o log.o mail.o main.o \ memory.o memoserv.o messages.o misc.o modules.o nickserv.o operserv.o \ process.o protocol.o send.o servers.o sessions.o slist.o sockutil.o opertype.o users.o module.o modulemanager.o configreader.o \ wildcard.o nickcore.o nickalias.o timers.o modes.o regchannel.o @@ -7,7 +7,7 @@ OBJS = actions.o base64.o bots.o botserv.o channels.o chanserv.o command.o comm INCLUDES = ../include/commands.h ../include/defs.h ../include/language.h \ ../include/pseudo.h ../include/sysconf.h ../include/config.h \ ../include/messages.h ../include/services.h \ - ../include/timers.h ../include/datafiles.h ../include/extern.h \ + ../include/timers.h ../include/extern.h \ ../include/modules.h ../include/slist.h ../include/hashcomp.h MAKEARGS = 'CFLAGS=${CFLAGS}' 'CC=${CC}' 'ANOPELIBS=${ANOPELIBS}' \ @@ -47,7 +47,6 @@ command.o: command.cpp $(INCLUDES) commands.o: commands.c $(INCLUDES) compat.o: compat.c $(INCLUDES) config.o: config.c $(INCLUDES) -datafiles.o: datafiles.c $(INCLUDES) encrypt.o: encrypt.c $(INCLUDES) init.o: init.c $(INCLUDES) ircd.o: ircd.c $(INCLUDES) diff --git a/src/chanserv.c b/src/chanserv.c index dc5345ed6..c0f8ac9bb 100644 --- a/src/chanserv.c +++ b/src/chanserv.c @@ -719,12 +719,9 @@ int check_kick(User * user, const char *chan, time_t chants) if (c) { - c->RemoveMode(NULL, CMODE_BAN, mask); - } - - c->SetMode(NULL, CMODE_BAN, mask); - if (c) + c->SetMode(NULL, CMODE_BAN, mask); ircdproto->SendKick(whosends(ci), c, user, "%s", reason); + } return 1; } diff --git a/src/config.c b/src/config.c index aa7577cbf..85eeed357 100644 --- a/src/config.c +++ b/src/config.c @@ -22,6 +22,7 @@ ServerConfig Config; static std::string Modules; static std::string EncModules; +static std::string DBModules; static std::string HostCoreModules; static std::string MemoCoreModules; static std::string BotCoreModules; @@ -180,12 +181,7 @@ bool ValidateEmailReg(ServerConfig *, const char *tag, const char *value, ValueI { if (Config.NSEmailReg) { - if (std::string(value) == "prenickdatabase") - { - if (data.GetValue().empty()) - throw ConfigException(std::string("The value for <") + tag + ":" + value + "> cannot be empty when e-mail registrations are enabled!"); - } - else if (std::string(value) == "preregexpire") + if (std::string(value) == "preregexpire") { if (!data.GetInteger()) throw ConfigException(std::string("The value for <") + tag + ":" + value + "> must be non-zero when e-mail registration are enabled!"); @@ -240,7 +236,7 @@ bool ValidateBotServ(ServerConfig *, const char *tag, const char *value, ValueIt { if (Config.s_BotServ) { - if (std::string(value) == "description" || std::string(value) == "database") + if (std::string(value) == "description") { if (data.GetValue().empty()) throw ConfigException(std::string("The value for <") + tag + ":" + value + "> cannot be empty when BotServ is enabled!"); @@ -258,7 +254,7 @@ bool ValidateHostServ(ServerConfig *, const char *tag, const char *value, ValueI { if (Config.s_HostServ) { - if (std::string(value) == "description" || std::string(value) == "database") + if (std::string(value) == "description") { if (data.GetValue().empty()) throw ConfigException(std::string("The value for <") + tag + ":" + value + "> cannot be empty when HostServ is enabled!"); @@ -613,6 +609,7 @@ int ServerConfig::Read(bool bail) {"networkinfo", "networkname", "", new ValueContainerChar(&Config.NetworkName), DT_CHARPTR, ValidateNotEmpty}, {"networkinfo", "nicklen", "0", new ValueContainerUInt(&Config.NickLen), DT_UINTEGER | DT_NORELOAD, ValidateNickLen}, {"options", "encryption", "", new ValueContainerString(&EncModules), DT_STRING | DT_NORELOAD, ValidateNotEmpty}, + {"options", "database", "", new ValueContainerString(&DBModules), DT_STRING | DT_NORELOAD, ValidateNotEmpty}, {"options", "userkey1", "0", new ValueContainerLUInt(&Config.UserKey1), DT_LUINTEGER, NoValidation}, {"options", "userkey2", "0", new ValueContainerLUInt(&Config.UserKey2), DT_LUINTEGER, NoValidation}, {"options", "userkey3", "0", new ValueContainerLUInt(&Config.UserKey3), DT_LUINTEGER, NoValidation}, @@ -645,9 +642,7 @@ int ServerConfig::Read(bool bail) {"options", "mlock", "+nrt", new ValueContainerString(&Config.MLock), DT_STRING, NoValidation}, {"nickserv", "nick", "NickServ", new ValueContainerChar(&Config.s_NickServ), DT_CHARPTR | DT_NORELOAD, ValidateNotEmpty}, {"nickserv", "description", "Nickname Registration Service", new ValueContainerChar(&Config.desc_NickServ), DT_CHARPTR | DT_NORELOAD, ValidateNotEmpty}, - {"nickserv", "database", "nick.db", new ValueContainerChar(&Config.NickDBName), DT_CHARPTR, ValidateNotEmpty}, {"nickserv", "emailregistration", "no", new ValueContainerBool(&Config.NSEmailReg), DT_BOOLEAN, NoValidation}, - {"nickserv", "prenickdatabase", "", new ValueContainerChar(&Config.PreNickDBName), DT_CHARPTR, ValidateEmailReg}, {"nickserv", "modules", "", new ValueContainerString(&NickCoreModules), DT_STRING, NoValidation}, {"nickserv", "forceemail", "no", new ValueContainerBool(&Config.NSForceEmail), DT_BOOLEAN, ValidateEmailReg}, {"nickserv", "defaults", "secure memosignon memoreceive", new ValueContainerString(&NSDefaults), DT_STRING, NoValidation}, @@ -677,7 +672,6 @@ int ServerConfig::Read(bool bail) {"mail", "dontquoteaddresses", "no", new ValueContainerBool(&Config.DontQuoteAddresses), DT_BOOLEAN, NoValidation}, {"chanserv", "nick", "ChanServ", new ValueContainerChar(&Config.s_ChanServ), DT_CHARPTR | DT_NORELOAD, ValidateNotEmpty}, {"chanserv", "description", "Channel Registration Service", new ValueContainerChar(&Config.desc_ChanServ), DT_CHARPTR | DT_NORELOAD, ValidateNotEmpty}, - {"chanserv", "database", "chan.db", new ValueContainerChar(&Config.ChanDBName), DT_CHARPTR, ValidateNotEmpty}, {"chanserv", "modules", "", new ValueContainerString(&ChanCoreModules), DT_STRING, NoValidation}, {"chanserv", "defaults", "keeptopic secure securefounder signkick", new ValueContainerString(&CSDefaults), DT_STRING, NoValidation}, {"chanserv", "maxregistered", "0", new ValueContainerUInt(&Config.CSMaxReg), DT_UINTEGER, NoValidation}, @@ -699,7 +693,6 @@ int ServerConfig::Read(bool bail) {"memoserv", "memoreceipt", "0", new ValueContainerUInt(&Config.MSMemoReceipt), DT_UINTEGER, NoValidation}, {"botserv", "nick", "", new ValueContainerChar(&Config.s_BotServ), DT_CHARPTR | DT_NORELOAD, NoValidation}, {"botserv", "description", "Bot Service", new ValueContainerChar(&Config.desc_BotServ), DT_CHARPTR | DT_NORELOAD, ValidateBotServ}, - {"botserv", "database", "bot.db", new ValueContainerChar(&Config.BotDBName), DT_CHARPTR, ValidateBotServ}, {"botserv", "modules", "", new ValueContainerString(&BotCoreModules), DT_STRING, NoValidation}, {"botserv", "defaults", "", new ValueContainerString(&BSDefaults), DT_STRING, NoValidation}, {"botserv", "minusers", "0", new ValueContainerUInt(&Config.BSMinUsers), DT_UINTEGER, ValidateBotServ}, @@ -711,15 +704,11 @@ int ServerConfig::Read(bool bail) {"botserv", "fantasycharacter", "!", new ValueContainerChar(&Config.BSFantasyCharacter), DT_CHARPTR, NoValidation}, {"hostserv", "nick", "", new ValueContainerChar(&Config.s_HostServ), DT_CHARPTR | DT_NORELOAD, NoValidation}, {"hostserv", "description", "vHost Service", new ValueContainerChar(&Config.desc_HostServ), DT_CHARPTR | DT_NORELOAD, ValidateHostServ}, - {"hostserv", "database", "hosts.db", new ValueContainerChar(&Config.HostDBName), DT_CHARPTR, ValidateHostServ}, {"hostserv", "modules", "", new ValueContainerString(&HostCoreModules), DT_STRING, NoValidation}, {"operserv", "nick", "OperServ", new ValueContainerChar(&Config.s_OperServ), DT_CHARPTR | DT_NORELOAD, ValidateNotEmpty}, {"operserv", "description", "Operator Service", new ValueContainerChar(&Config.desc_OperServ), DT_CHARPTR | DT_NORELOAD, ValidateNotEmpty}, {"operserv", "globalnick", "Global", new ValueContainerChar(&Config.s_GlobalNoticer), DT_CHARPTR | DT_NORELOAD, ValidateNotEmpty}, {"operserv", "globaldescription", "Global Noticer", new ValueContainerChar(&Config.desc_GlobalNoticer), DT_CHARPTR | DT_NORELOAD, ValidateNotEmpty}, - {"operserv", "database", "oper.db", new ValueContainerChar(&Config.OperDBName), DT_CHARPTR, ValidateNotEmpty}, - {"operserv", "newsdatabase", "news.db", new ValueContainerChar(&Config.NewsDBName), DT_CHARPTR, ValidateNotEmpty}, - {"operserv", "exceptiondatabase", "exception.db", new ValueContainerChar(&Config.ExceptionDBName), DT_CHARPTR, ValidateNotEmpty}, {"operserv", "modules", "", new ValueContainerString(&OperCoreModules), DT_STRING, NoValidation}, {"operserv", "superadmin", "no", new ValueContainerBool(&Config.SuperAdmin), DT_BOOLEAN, NoValidation}, {"operserv", "logmaxusers", "no", new ValueContainerBool(&Config.LogMaxUsers), DT_BOOLEAN, NoValidation}, @@ -1689,6 +1678,7 @@ int read_config(int reload) /* Modules Autoload building... :P */ Config.ModulesAutoLoad = BuildStringList(!Modules.empty() ? Modules : ""); Config.EncModuleList = BuildStringList(!EncModules.empty() ? EncModules : ""); + Config.DBModuleList = BuildStringList(!DBModules.empty() ? DBModules : ""); Config.HostServCoreModules = BuildStringList(!HostCoreModules.empty() ? HostCoreModules : ""); Config.MemoServCoreModules = BuildStringList(!MemoCoreModules.empty() ? MemoCoreModules : ""); Config.BotServCoreModules = BuildStringList(!BotCoreModules.empty() ? BotCoreModules : ""); @@ -1775,18 +1765,6 @@ int read_config(int reload) } } - /** - * If they try to enable any email registration option, - * make sure they have everything else they need too... - * - * rob - **/ - if (!Config.NSEmailReg) { - delete [] Config.PreNickDBName; - Config.PreNickDBName = NULL; - Config.NSRExpire = 0; - } - SetDefaultMLock(); if (!retval) { diff --git a/src/core/db_plain.cpp b/src/core/db_plain.cpp new file mode 100644 index 000000000..b551d0b5c --- /dev/null +++ b/src/core/db_plain.cpp @@ -0,0 +1,1148 @@ +/* + * + * (C) 2003-2009 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + * + * $Id$ + * + */ +/*************************************************************************/ + +#include "module.h" + +std::fstream db; + +/** Enum used for what METADATA type we are reading + */ +enum MDType +{ + MD_NONE, + MD_NC, + MD_NA, + MD_BI, + MD_CH +}; + +/* Character for newlines, in my research I have concluded it is significantly faster + * to use \n instead of std::endl because std::endl also flushes the buffer, which + * is not necessary here + */ +static const char endl = '\n'; + +/** Read from the database and call the events + * @param m If specified it only calls the read events for the specific module + */ +static void ReadDatabase(Module *m = NULL) +{ + std::string buf; + EventReturn MOD_RESULT; + MDType Type = MD_NONE; + + db.open(DATABASE_FILE, std::ios_base::in); + + if (!db.is_open()) + { + ircdproto->SendGlobops(NULL, "Unable to open %s for reading!", DATABASE_FILE); + return; + } + + while (std::getline(db, buf)) + { + if (buf.empty()) + continue; + + spacesepstream sep(buf); + std::vector params; + while (sep.GetToken(buf)) + { + if (buf[0] == ':') + { + buf.erase(buf.begin()); + if (!buf.empty()) + params.push_back(buf + " " + sep.GetRemaining()); + } + else + params.push_back(buf); + } + + if (m) + MOD_RESULT = m->OnDatabaseRead(params); + else + { + FOREACH_RESULT(I_OnDatabaseRead, OnDatabaseRead(params)); + } + if (MOD_RESULT == EVENT_STOP) + continue; + + std::string mdbuf; + NickCore *nc; + NickAlias *na; + BotInfo *bi; + ChannelInfo *ci; + if (!params.empty()) + { + if (params[0] == "NC") + { + nc = findcore(params[1].c_str()); + Type = MD_NC; + } + else if (params[0] == "NA") + { + na = findnick(params[2].c_str()); + Type = MD_NA; + } + else if (params[0] == "BI") + { + bi = findbot(params[1].c_str()); + Type = MD_BI; + } + else if (params[0] == "CH") + { + ci = cs_findchan(params[1].c_str()); + Type = MD_CH; + } + else if (params[0] == "MD") + { + std::string key = params[1]; + params.erase(params.begin()); + params.erase(params.begin()); + + if (nc && Type == MD_NC) + { + try + { + if (m) + MOD_RESULT = m->OnDatabaseReadMetadata(nc, key, params); + else + { + FOREACH_RESULT(I_OnDatabaseReadMetadata, OnDatabaseReadMetadata(nc, key, params)); + } + } + catch (const char *err) + { + alog("[db_plain}: %s", err); + } + } + else if (na && Type == MD_NA) + { + try + { + if (m) + MOD_RESULT = m->OnDatabaseReadMetadata(na, key, params); + else + { + FOREACH_RESULT(I_OnDatabaseReadMetadata, OnDatabaseReadMetadata(na, key, params)); + } + } + catch (const char *err) + { + alog("[db_plain}: %s", err); + } + } + else if (bi && Type == MD_BI) + { + try + { + if (m) + MOD_RESULT = m->OnDatabaseReadMetadata(bi, key, params); + else + { + FOREACH_RESULT(I_OnDatabaseReadMetadata, OnDatabaseReadMetadata(bi, key, params)); + } + } + catch (const char *err) + { + alog("[db_plain}: %s", err); + } + } + else if (ci && Type == MD_CH) + { + try + { + if (m) + MOD_RESULT = m->OnDatabaseReadMetadata(ci, key, params); + else + { + FOREACH_RESULT(I_OnDatabaseReadMetadata, OnDatabaseReadMetadata(ci, key, params)); + } + } + catch (const char *err) + { + alog("[db_plain}: %s", err); + } + } + } + } + } + + db.close(); +} + +struct LangInfo +{ + std::string Name; + int LanguageId; +}; + +struct ChannelModeInfo +{ + std::string Name; + ChannelModeName Value; +}; + +struct ChannelFlagInfo +{ + std::string Name; + ChannelInfoFlag Flag; +}; + +struct ChannelLevel +{ + std::string Name; + int Level; +}; + +struct BotFlagInfo +{ + std::string Name; + BotServFlag Flag; +}; + +struct NickCoreFlagInfo +{ + std::string Name; + NickCoreFlag Flag; +}; + +LangInfo LangInfos[] = { + {"en", LANG_EN_US}, + {"es", LANG_ES}, + {"pt", LANG_PT}, + {"fr", LANG_FR}, + {"tr", LANG_TR}, + {"it", LANG_IT}, + {"de", LANG_DE}, + {"ca", LANG_CAT}, + {"gr", LANG_GR}, + {"nl", LANG_NL}, + {"ru", LANG_RU}, + {"hu", LANG_HUN}, + {"pl", LANG_PL}, + {"", -1} +}; + +ChannelModeInfo ChannelModes[] = { + {"BLOCKCOLOR", CMODE_BLOCKCOLOR}, + {"FLOOD", CMODE_FLOOD}, + {"INVITE", CMODE_INVITE}, + {"KEY", CMODE_KEY}, + {"LIMIT", CMODE_LIMIT}, + {"MODERATED", CMODE_MODERATED}, + {"NOEXTERNAL", CMODE_NOEXTERNAL}, + {"PRIVATE", CMODE_PRIVATE}, + {"REGISTERED", CMODE_REGISTERED}, + {"SECRET", CMODE_SECRET}, + {"TOPIC", CMODE_TOPIC}, + {"AUDITORIUM", CMODE_AUDITORIUM}, + {"SSL", CMODE_SSL}, + {"ADMINONLY", CMODE_ADMINONLY}, + {"NOCTCP", CMODE_NOCTCP}, + {"FILTER", CMODE_FILTER}, + {"NOKNOCK", CMODE_NOKNOCK}, + {"REDIRECT", CMODE_REDIRECT}, + {"REGMODERATED", CMODE_REGMODERATED}, + {"NONICK", CMODE_NONICK}, + {"OPERONLY", CMODE_OPERONLY}, + {"NONICK", CMODE_NONICK}, + {"REGISTEREDONLY", CMODE_REGISTEREDONLY}, + {"STRIPCOLOR", CMODE_STRIPCOLOR}, + {"NONOTICE", CMODE_NONOTICE}, + {"NOINVITE", CMODE_NOINVITE}, + {"ALLINVITE", CMODE_ALLINVITE}, + {"BLOCKCAPS", CMODE_BLOCKCAPS}, + {"PERM", CMODE_PERM}, + {"NICKFLOOD", CMODE_NICKFLOOD}, + {"JOINFLOOD", CMODE_JOINFLOOD}, + {"", static_cast(-1)} +}; + +ChannelFlagInfo ChannelInfoFlags[] = { + {"KEEPTOPIC", CI_KEEPTOPIC}, + {"SECUREOPS", CI_SECUREOPS}, + {"PRIVATE", CI_PRIVATE}, + {"TOPICLOCK", CI_TOPICLOCK}, + {"RESTRICTED", CI_RESTRICTED}, + {"PEACE", CI_PEACE}, + {"SECURE", CI_SECURE}, + {"FORBIDDEN", CI_FORBIDDEN}, + {"NO_EXPIRE", CI_NO_EXPIRE}, + {"MEMO_HARDMAX", CI_MEMO_HARDMAX}, + {"OPNOTICE", CI_OPNOTICE}, + {"SECUREFOUNDER", CI_SECUREFOUNDER}, + {"SIGNKICK", CI_SIGNKICK}, + {"SIGNKICK_LEVEL", CI_SIGNKICK_LEVEL}, + {"XOP", CI_XOP}, + {"SUSPENDED", CI_SUSPENDED}, + {"PERSIST", CI_PERSIST}, + {"", static_cast(-1)} +}; + +ChannelLevel ChannelLevels[] = { + {"INVITE", CA_INVITE}, + {"AKICK", CA_AKICK}, + {"SET", CA_SET}, + {"UNBAN", CA_UNBAN}, + {"AUTOOP", CA_AUTOOP}, + {"AUTODEOP", CA_AUTODEOP}, + {"AUTOVOICE", CA_AUTOVOICE}, + {"OPDEOP", CA_OPDEOP}, + {"ACCESS_LIST", CA_ACCESS_LIST}, + {"CLEAR", CA_CLEAR}, + {"NOJOIN", CA_NOJOIN}, + {"ACCESS_CHANGE", CA_ACCESS_CHANGE}, + {"MEMO", CA_MEMO}, + {"ASSIGN", CA_ASSIGN}, + {"BADWORDS", CA_BADWORDS}, + {"NOKICK", CA_NOKICK}, + {"FANTASIA", CA_FANTASIA}, + {"SAY", CA_SAY}, + {"GREET", CA_GREET}, + {"VOICEME", CA_VOICEME}, + {"VOICE", CA_VOICE}, + {"GETKEY", CA_GETKEY}, + {"AUTOHALFOP", CA_AUTOHALFOP}, + {"AUTOPROTECT", CA_AUTOPROTECT}, + {"OPDEOPME", CA_OPDEOPME}, + {"HALFOPME", CA_HALFOPME}, + {"HALFOP", CA_HALFOP}, + {"PROTECTME", CA_PROTECTME}, + {"PROTECT", CA_PROTECT}, + {"KICKME", CA_KICKME}, + {"KICK", CA_KICK}, + {"SIGNKICK", CA_SIGNKICK}, + {"BANME", CA_BANME}, + {"BAN", CA_BAN}, + {"TOPIC", CA_TOPIC}, + {"INFO", CA_INFO}, + {"AUTOOWNER", CA_AUTOOWNER}, + {"OWNER", CA_OWNER}, + {"OWNERME", CA_OWNERME}, + {"", -1} +}; + +BotFlagInfo BotFlags[] = { + {"DONTKICKOPS", BS_DONTKICKOPS}, + {"DONTKICKVOICES", BS_DONTKICKVOICES}, + {"FANTASY", BS_FANTASY}, + {"SYMBIOSIS", BS_SYMBIOSIS}, + {"GREET", BS_GREET}, + {"NOBOT", BS_NOBOT}, + {"KICK_BOLDS", BS_KICK_BOLDS}, + {"KICK_COLORS", BS_KICK_COLORS}, + {"KICK_REVERSES", BS_KICK_REVERSES}, + {"KICK_UNDERLINES", BS_KICK_UNDERLINES}, + {"KICK_BADWORDS", BS_KICK_BADWORDS}, + {"KICK_CAPS", BS_KICK_CAPS}, + {"KICK_FLOOD", BS_KICK_FLOOD}, + {"KICK_REPEAT", BS_KICK_REPEAT}, + {"", static_cast(-1)} +}; + +NickCoreFlagInfo NickCoreFlags[] = { + {"KILLPROTECT", NI_KILLPROTECT}, + {"SECURE", NI_SECURE}, + {"MSG", NI_MSG}, + {"MEMO_HARDMAX", NI_MEMO_HARDMAX}, + {"MEMO_SIGNON", NI_MEMO_SIGNON}, + {"MEMO_RECEIVE", NI_MEMO_RECEIVE}, + {"PRIVATE", NI_PRIVATE}, + {"HIDE_EMAIL", NI_HIDE_EMAIL}, + {"HIDE_MASK", NI_HIDE_MASK}, + {"HIDE_QUIT", NI_HIDE_QUIT}, + {"KILL_QUICK", NI_KILL_QUICK}, + {"KILL_IMMED", NI_KILL_IMMED}, + {"MEMO_MAIL", NI_MEMO_MAIL}, + {"HIDE_STATUS", NI_HIDE_STATUS}, + {"SUSPENDED", NI_SUSPENDED}, + {"AUTOOP", NI_AUTOOP}, + {"FORBIDDEN", NI_FORBIDDEN}, + {"", static_cast(-1)} +}; + +static void LoadNickCore(const std::vector ¶ms) +{ + NickCore *nc = new NickCore(params[0]); + /* Clear default flags */ + nc->ClearFlags(); + + if (params.size() <= 2) + { + /* This is a forbidden nick */ + return; + } + + size_t tmp = params[1].find(':'); // XXX till we store the hash method internally with the nickcore + const char *pass = params[1].c_str() + tmp + 1; + char passbuf[PASSMAX]; + memset(&passbuf, 0, sizeof(passbuf)); + b64_decode(pass, passbuf, PASSMAX); + strscpy(nc->pass, passbuf, PASSMAX); + + for (int i = 0; LangInfos[i].LanguageId != -1; ++i) + if (params[2] == LangInfos[i].Name) + nc->language = LangInfos[i].LanguageId; + + nc->memos.memomax = atoi(params[3].c_str()); + nc->channelcount = atoi(params[4].c_str()); + + if (debug > 1) + alog("[db_plain]: Loaded NickCore %s", nc->display); +} + +static void LoadNickAlias(const std::vector ¶ms) +{ + NickCore *nc = findcore(params[0].c_str()); + if (!nc) + { + alog("[db_plain]: Unable to find core %s", params[0].c_str()); + return; + } + + NickAlias *na = new NickAlias(params[1], nc); + + na->time_registered = strtol(params[2].c_str(), NULL, 10); + + na->last_seen = strtol(params[3].c_str(), NULL, 10); + + if (debug > 1) + alog("[db_plain}: Loaded nickalias for %s", na->nick); +} + +static void LoadBotInfo(const std::vector ¶ms) +{ + BotInfo *bi = new BotInfo(params[0].c_str()); + bi->user = sstrdup(params[1].c_str()); + bi->host = sstrdup(params[2].c_str()); + bi->created = strtol(params[4].c_str(), NULL, 10); + bi->chancount = atol(params[5].c_str()); + bi->real = sstrdup(params[6].c_str()); + + if (debug > 1) + alog("[db_plain]: Loaded botinfo for %s", bi->nick); +} + +static void LoadChanInfo(const std::vector ¶ms) +{ + ChannelInfo *ci = new ChannelInfo(params[0]); + /* CLear default mlock */ + ci->ClearMLock(); + /* Remove default channel flags */ + ci->ClearFlags(); + ci->botflags.ClearFlags(); + + ci->time_registered = strtol(params[1].c_str(), NULL, 10); + + ci->last_used = strtol(params[2].c_str(), NULL, 10); + + ci->bantype = atoi(params[3].c_str()); + + ci->memos.memomax = atoi(params[4].c_str()); + + if (debug > 1) + alog("[db_plain]: loaded channel %s", ci->name); +} + +static void LoadHostInfo(const std::vector ¶ms) +{ + if (!findnick(params[0].c_str())) + return; + addHostCore(params[0].c_str(), params[4].empty() ? NULL : const_cast(params[4].c_str()), const_cast(params[3].c_str()), params[1].c_str(), strtol(params[2].c_str(), NULL, 10)); // XXX + if (debug > 1) + alog("[db_plain]: loaded vhost for %s", params[0].c_str()); +} + +static void LoadOperInfo(const std::vector ¶ms) +{ + if (params[0] == "STATS") + { + maxusercnt = atol(params[1].c_str()); + maxusertime = strtol(params[2].c_str(), NULL, 10); + } + else if (params[0] == "AKILL" || params[0] == "SGLINE" || params[0] == "SQLINE" || params[0] == "SZLINE") + { + int (*add_sxline)(User *, const char *, const char *, time_t, const char *); + if (params[0] == "AKILL") + add_sxline = add_akill; + else if (params[0] == "SGLINE") + add_sxline = add_sgline; + else if (params[0] == "SQLINE") + add_sxline = add_sqline; + else if (params[0] == "SZLINE") + add_sxline = add_szline; + + add_sxline(NULL, (params[1] + "@" + params[2]).c_str(), params[3].c_str(), strtol(params[5].c_str(), NULL, 10), params[6].c_str()); + } + else if (params[0] == "EXCEPTION") + { + nexceptions++; + exceptions = static_cast(srealloc(exceptions, sizeof(Exception) * nexceptions)); + exceptions[nexceptions - 1].mask = sstrdup(params[1].c_str()); + exceptions[nexceptions - 1].limit = atol(params[2].c_str()); + strscpy(exceptions[nexceptions - 1].who, params[3].c_str(), NICKMAX); + exceptions[nexceptions - 1].time = strtol(params[4].c_str(), NULL, 10); + exceptions[nexceptions - 1].expires = strtol(params[5].c_str(), NULL, 10); + exceptions[nexceptions - 1].reason = sstrdup(params[6].c_str()); + exceptions[nexceptions - 1].num = nexceptions - 1; + } +} + +void Write(const std::string &buf) +{ + db << buf << endl; +} + +void WriteMetadata(const std::string &key, const std::string &data) +{ + Write("MD " + key + " " + data); +} + +class DBPlain : public Module +{ + public: + DBPlain(const std::string &modname, const std::string &creator) : Module(modname, creator) + { + this->SetAuthor("Anope"); + this->SetVersion("$Id$"); + this->SetType(DATABASE); + + Implementation i[] = { I_OnDatabaseRead, I_OnLoadDatabase, I_OnDatabaseReadMetadata, I_OnSaveDatabase, I_OnModuleLoad }; + ModuleManager::Attach(i, this, 5); + } + + EventReturn OnDatabaseRead(const std::vector ¶ms) + { + std::string key = params[0]; + std::vector otherparams = params; + otherparams.erase(otherparams.begin()); + + if (key == "NC") + LoadNickCore(otherparams); + else if (key == "NA") + LoadNickAlias(otherparams); + else if (key == "BI") + LoadBotInfo(otherparams); + else if (key == "CH") + LoadChanInfo(otherparams); + else if (key == "HI") + LoadHostInfo(otherparams); + else if (key == "OS") + LoadOperInfo(otherparams); + + return EVENT_CONTINUE; + } + + EventReturn OnLoadDatabase() + { + ReadDatabase(); + + /* No need to ever reload this again, although this should never be trigged again */ + ModuleManager::Detach(I_OnLoadDatabase, this); + ModuleManager::Detach(I_OnDatabaseReadMetadata, this); + + return EVENT_STOP; + } + + EventReturn OnDatabaseReadMetadata(NickCore *nc, const std::string &key, const std::vector ¶ms) + { + if (key == "EMAIL") + nc->email = sstrdup(params[0].c_str()); + else if (key == "GREET") + nc->greet = sstrdup(params[0].c_str()); + else if (key == "ICQ") + nc->icq = atoi(params[0].c_str()); + else if (key == "URL") + nc->url = sstrdup(params[0].c_str()); + else if (key == "ACCESS") + nc->AddAccess(params[0]); + else if (key == "FLAGS") + { + for (unsigned j = 0; j < params.size(); ++j) + for (int i = 0; NickCoreFlags[i].Flag != -1; ++i) + if (NickCoreFlags[i].Name == params[j]) + nc->SetFlag(NickCoreFlags[i].Flag); + } + else if (key == "MI") + { + Memo *m = new Memo; + m->number = atoi(params[0].c_str()); + m->time = strtol(params[1].c_str(), NULL, 10); + strscpy(m->sender, params[2].c_str(), NICKMAX); + unsigned j; + for (j = 3; (params[j] == "UNREAD" || params[j] == "RECEIPT" || params[j] == "NOTIFYS"); ++j) + { + if (params[j] == "UNREAD") + m->SetFlag(MF_UNREAD); + else if (params[j] == "RECEIPT") + m->SetFlag(MF_RECEIPT); + else if (params[j] == "NOTIFYS") + m->SetFlag(MF_NOTIFYS); + } + m->text = sstrdup(params[j + 1].c_str()); + nc->memos.memos.push_back(m); + } + + return EVENT_CONTINUE; + } + + EventReturn OnDatabaseReadMetadata(NickAlias *na, const std::string &key, const std::vector ¶ms) + { + if (key == "LAST_USERMASK") + na->last_usermask = sstrdup(params[0].c_str()); + else if (key == "LAST_REALNAME") + na->last_realname = sstrdup(params[0].c_str()); + else if (key == "LAST_QUIT") + na->last_quit = sstrdup(params[0].c_str()); + else if (key == "FLAGS") + { + for (unsigned j = 0; j < params.size(); ++j) + { + if (params[j] == "FORBIDDEN") + na->SetFlag(NS_FORBIDDEN); + else if (params[j] == "NOEXPIRE") + na->SetFlag(NS_NO_EXPIRE); + } + } + + return EVENT_CONTINUE; + } + + EventReturn OnDatabaseReadMetadata(BotInfo *bi, const std::string &key, const std::vector ¶ms) + { + std::string buf; + + if (key == "FLAGS") + { + for (unsigned j = 0; j < params.size(); ++j) + { + if (params[j] == "PRIVATE") + bi->SetFlag(BI_PRIVATE); + else if (params[j] == "CHANSERV") + bi->SetFlag(BI_CHANSERV); + else if (params[j] == "BOTSERV") + bi->SetFlag(BI_BOTSERV); + else if (params[j] == "HOSTSERV") + bi->SetFlag(BI_HOSTSERV); + else if (params[j] == "OPERSERV") + bi->SetFlag(BI_OPERSERV); + else if (params[j] == "MEMOSERV") + bi->SetFlag(BI_MEMOSERV); + else if (params[j] == "NICKSERV") + bi->SetFlag(BI_NICKSERV); + else if (params[j] == "GLOBAL") + bi->SetFlag(BI_GLOBAL); + } + } + + return EVENT_CONTINUE; + } + + EventReturn OnDatabaseReadMetadata(ChannelInfo *ci, const std::string &key, const std::vector ¶ms) + { + std::string buf; + int i; + + if (key == "FOUNDER") + { + ci->founder = findcore(params[0].c_str()); + if (!ci->founder) + { + alog("[db_plain]: Deleting founderless channel %s", ci->name); + delete ci; + ci = NULL; + throw "no founder"; + } + } + else if (key == "SUCCESSOR") + ci->successor = findcore(params[0].c_str()); + else if (key == "LEVELS") + { + for (unsigned j = 0; j < params.size(); ++j, ++j) + for (i = 0; ChannelLevels[i].Level != -1; ++i) + if (ChannelLevels[i].Name == params[j]) + ci->levels[ChannelLevels[i].Level] = atoi(params[j + 1].c_str()); + } + else if (key == "FLAGS") + { + for (unsigned j = 0; j < params.size(); ++j) + for (i = 0; ChannelInfoFlags[i].Flag != -1; ++i) + if (ChannelInfoFlags[i].Name == params[j]) + ci->SetFlag(ChannelInfoFlags[i].Flag); + } + else if (key == "DESC") + ci->desc = sstrdup(params[0].c_str()); + else if (key == "URL") + ci->url = sstrdup(params[0].c_str()); + else if (key == "EMAIL") + ci->email = sstrdup(params[0].c_str()); + else if (key == "TOPIC") + { + strscpy(ci->last_topic_setter, params[0].c_str(), NICKMAX); + ci->last_topic_time = strtol(params[1].c_str(), NULL, 10); + ci->last_topic = sstrdup(params[2].c_str()); + } + else if (key == "FORBID") + { + ci->forbidby = sstrdup(params[0].c_str()); + ci->forbidreason = sstrdup(params[1].c_str()); + } + else if (key == "ACCESS") + { + NickCore *nc = findcore(params[0].c_str()); + if (!nc) + throw "access entry for nonexistant core"; + int level = atoi(params[1].c_str()); + time_t last_seen = strtol(params[2].c_str(), NULL, 10); + ci->AddAccess(nc, level, buf, last_seen); + } + else if (key == "AKICK") + { + bool Stuck = params[0] == "STUCK" ? true : false; + bool Nick = params[1] == "NICK" ? true : false; + NickCore *nc = NULL; + if (Nick) + { + nc = findcore(params[2].c_str()); + if (!nc) + { + throw "akick for nonexistant core"; + } + } + AutoKick *ak; + if (Nick) + ak = ci->AddAkick(params[3], nc, params.size() > 5 ? params[5] : "", strtol(params[4].c_str(), NULL, 10)); + else + ak = ci->AddAkick(params[3], params[2], params.size() > 5 ? params[5] : "", strtol(params[4].c_str(), NULL, 10)); + if (Stuck) + ak->SetFlag(AK_STUCK); + if (Nick) + ak->SetFlag(AK_ISNICK); + + } + else if (key == "MLOCK_ON" || buf == "MLOCK_OFF") + { + bool Set = key == "MLOCK_ON" ? true : false; + + for (unsigned j = 0; j < params.size(); ++j) + for (i = 0; ChannelModes[i].Value != -1; ++i) + if (ChannelModes[i].Name == params[j]) + ci->SetMLock(ChannelModes[i].Value, Set); + } + else if (key == "MLP") + { + for (unsigned j = 0; j < params.size(); ++j, ++j) + for (i = 0; ChannelModes[i].Value != -1; ++i) + if (ChannelModes[i].Name == params[j]) + ci->SetMLock(ChannelModes[i].Value, true, params[j + 1]); + } + else if (key == "MI") + { + Memo *m = new Memo; + m->number = atoi(params[0].c_str()); + m->time = strtol(params[1].c_str(), NULL, 10); + strscpy(m->sender, params[2].c_str(), NICKMAX); + unsigned j; + for (j = 3; j < params.size() - 1 && (buf == "UNREAD" || buf == "RECEIPT" || buf == "NOTIFYS"); ++j) + { + if (params[j] == "UNREAD") + m->SetFlag(MF_UNREAD); + else if (params[j] == "RECEIPT") + m->SetFlag(MF_RECEIPT); + else if (params[j] == "NOTIFYS") + m->SetFlag(MF_NOTIFYS); + } + m->text = sstrdup(params[j + 1].c_str()); + ci->memos.memos.push_back(m); + } + else if (key == "ENTRYMSG") + ci->entry_message = sstrdup(params[0].c_str()); + else if (key == "BI") + { + if (params[0] == "NAME") + ci->bi = findbot(params[1].c_str()); + else if (params[0] == "FLAGS") + { + for (unsigned j = 1; j < params.size(); ++j) + for (i = 0; BotFlags[i].Flag != -1; ++i) + if (BotFlags[i].Name == params[j]) + ci->botflags.SetFlag(BotFlags[i].Flag); + } + else if (params[0] == "TTB") + { + for (unsigned j = 1; j < params.size(); ++j, ++j) + { + if (params[j] == "BOLDS") + ci->ttb[0] = atoi(params[j + 1].c_str()); + else if (params[j] == "COLORS") + ci->ttb[1] = atoi(params[j + 1].c_str()); + else if (params[j] == "REVERSES") + ci->ttb[2] = atoi(params[j + 1].c_str()); + else if (params[j] == "UNDERLINES") + ci->ttb[3] = atoi(params[j + 1].c_str()); + else if (params[j] == "BADWORDS") + ci->ttb[4] = atoi(params[j + 1].c_str()); + else if (params[j] == "CAPS") + ci->ttb[5] = atoi(params[j + 1].c_str()); + else if (params[j] == "FLOOD") + ci->ttb[6] = atoi(params[j + 1].c_str()); + else if (params[j] == "REPEAT") + ci->ttb[7] = atoi(params[j + 1].c_str()); + } + } + else if (params[0] == "CAPSMIN") + ci->capsmin = atoi(params[1].c_str()); + else if (params[0] == "CAPSPERCENT") + ci->capspercent = atoi(params[1].c_str()); + else if (params[0] == "FLOODLINES") + ci->floodlines = atoi(params[1].c_str()); + else if (params[0] == "FLOODSECS") + ci->floodsecs = atoi(params[1].c_str()); + else if (params[0] == "REPEATTIMES") + ci->repeattimes = atoi(params[1].c_str()); + else if (params[0] == "BADWORD") + { + BadWordType Type; + if (params[1] == "SINGLE") + Type = BW_SINGLE; + else if (params[1] == "START") + Type = BW_START; + else if (params[1] == "END") + Type = BW_END; + else + Type = BW_ANY; + ci->AddBadWord(params[2], Type); + } + } + + return EVENT_CONTINUE; + } + + EventReturn OnSaveDatabase() + { + db.open(DATABASE_FILE, std::ios_base::out | std::ios_base::trunc); + + if (!db.is_open()) + { + ircdproto->SendGlobops(NULL, "Unable to open %s for writing!", DATABASE_FILE); + return EVENT_CONTINUE; + } + + db << "VER 1" << endl; + + int i, j; + unsigned k; + NickCore *nc; + for (i = 0; i < 1024; ++i) + { + for (nc = nclists[i]; nc; nc = nc->next) + { + if (nc->HasFlag(NI_FORBIDDEN)) + { + db << "NC " << nc->display << endl; + db << "MD FLAGS FORBIDDEN" << endl; + FOREACH_MOD(I_OnDatabaseWriteMetadata, OnDatabaseWriteMetadata(WriteMetadata, nc)); + continue; + } + else + { + //XXX forced plain till we store hashm in pw + char temppass[5000]; + b64_encode(nc->pass, strlen(nc->pass), temppass, 5000); + db << "NC " << nc->display << " plain:" << temppass << " "; + } + for (j = 0; LangInfos[j].LanguageId != -1; ++j) + if (nc->language == LangInfos[j].LanguageId) + db << LangInfos[j].Name; + db << " " << nc->memos.memomax << " " << nc->channelcount << endl; + + if (nc->email) + db << "MD EMAIL " << nc->email << endl; + if (nc->greet) + db << "MD GREET :" << nc->greet << endl; + if (nc->icq) + db << "MD ICQ :" << nc->icq << endl; + if (nc->url) + db << "MD URL :" << nc->url << endl; + if (!nc->access.empty()) + { + for (std::vector::iterator it = nc->access.begin(); it != nc->access.end(); ++it) + db << "MD ACCESS " << *it << endl; + } + if (nc->FlagCount()) + { + db << "MD FLAGS"; + for (j = 0; NickCoreFlags[j].Flag != -1; ++j) + if (nc->HasFlag(NickCoreFlags[j].Flag)) + db << " " << NickCoreFlags[j].Name; + db << endl; + } + if (!nc->memos.memos.empty()) + { + MemoInfo *mi = &nc->memos; + for (k = 0; k < mi->memos.size(); ++k) + { + db << "MD MI " << mi->memos[k]->number << " " << mi->memos[k]->time << " " << mi->memos[k]->sender; + if (mi->memos[k]->HasFlag(MF_UNREAD)) + db << " UNREAD"; + if (mi->memos[k]->HasFlag(MF_RECEIPT)) + db << " RECEIPT"; + if (mi->memos[k]->HasFlag(MF_NOTIFYS)) + db << " NOTIFYS"; + db << " :" << mi->memos[k]->text << endl; + } + } + + FOREACH_MOD(I_OnDatabaseWriteMetadata, OnDatabaseWriteMetadata(WriteMetadata, nc)); + + } + } + + NickAlias *na; + for (i = 0; i < 1024; ++i) + { + for (na = nalists[i]; na; na = na->next) + { + db << "NA " << na->nc->display << " " << na->nick << " " << na->time_registered << " " << na->last_seen << endl; + if (na->last_usermask) + db << "MD LAST_USERMASK " << na->last_usermask << endl; + if (na->last_realname) + db << "MD LAST_REALNAME :" << na->last_realname << endl; + if (na->last_quit) + db << "MD LAST_QUIT :" << na->last_quit << endl; + if (na->HasFlag(NS_FORBIDDEN) || na->HasFlag(NS_NO_EXPIRE)) + { + db << "MD FLAGS" << (na->HasFlag(NS_FORBIDDEN) ? " FORBIDDEN" : "") << (na->HasFlag(NS_NO_EXPIRE) ? " NOEXPIRE " : "") << endl; + } + + FOREACH_MOD(I_OnDatabaseWriteMetadata, OnDatabaseWriteMetadata(WriteMetadata, na)); + } + } + + BotInfo *bi; + for (i = 0; i < 256; ++i) + { + for (bi = botlists[i]; bi; bi = bi->next) + { + db << "BI " << bi->nick << " " << bi->user << " " << bi->host << " " << bi->created << " " << bi->chancount << " :" << bi->real << endl; + if (bi->FlagCount()) + { + db << "MD FLAGS"; + if (bi->HasFlag(BI_PRIVATE)) + db << " PRIVATE"; + if (bi->HasFlag(BI_CHANSERV)) + db << " CHANSERV"; + else if (bi->HasFlag(BI_BOTSERV)) + db << " BOTSERV"; + else if (bi->HasFlag(BI_HOSTSERV)) + db << " HOSTSERV"; + else if (bi->HasFlag(BI_OPERSERV)) + db << " OPERSERV"; + else if (bi->HasFlag(BI_MEMOSERV)) + db << " MEMOSERV"; + else if (bi->HasFlag(BI_NICKSERV)) + db << " NICKSERV"; + else if (bi->HasFlag(BI_GLOBAL)) + db << " GLOBAL"; + db << endl; + } + + FOREACH_MOD(I_OnDatabaseWriteMetadata, OnDatabaseWriteMetadata(WriteMetadata, bi)); + } + } + + ChannelInfo *ci; + for (i = 0; i < 256; ++i) + { + for (ci = chanlists[i]; ci; ci = ci->next) + { + db << "CH " << ci->name << " " << ci->time_registered << " " << ci->last_used; + db << " " << ci->bantype << " " << ci->memos.memomax << endl; + if (ci->founder) + db << "MD FOUNDER " << ci->founder->display << endl; + if (ci->successor) + db << "MD SUCCESSOR " << ci->successor->display << endl; + if (ci->desc) + db << "MD DESC :" << ci->desc << endl; + if (ci->url) + db << "MD URL :" << ci->url << endl; + if (ci->email) + db << "MD EMAIL :" << ci->email << endl; + if (ci->last_topic) + db << "MD TOPIC " << ci->last_topic_setter << " " << ci->last_topic_time << " :" << ci->last_topic << endl; + db << "MD LEVELS"; + for (j = 0; ChannelLevels[j].Level != -1; ++j) + db << " " << ChannelLevels[j].Name << " " << ci->levels[ChannelLevels[j].Level]; + db << endl; + if (ci->FlagCount()) + { + db << "MD FLAGS"; + for (j = 0; ChannelInfoFlags[j].Flag != -1; ++j) + if (ci->HasFlag(ChannelInfoFlags[j].Flag)) + db << " " << ChannelInfoFlags[j].Name; + db << endl; + if (ci->HasFlag(CI_FORBIDDEN)) + db << "MD FORBID " << ci->forbidby << " :" << ci->forbidreason << endl; + } + for (k = 0; k < ci->GetAccessCount(); ++k) + if (ci->GetAccess(k)->in_use) + db << "MD ACCESS " << ci->GetAccess(k)->nc->display << " " << ci->GetAccess(k)->level << " " + << ci->GetAccess(k)->last_seen << " " << ci->GetAccess(k)->creator << endl; + for (k = 0; k < ci->GetAkickCount(); ++k) + { + db << "MD AKICK " + << (ci->GetAkick(k)->HasFlag(AK_STUCK) ? "STUCK " : "UNSTUCK ") + << (ci->GetAkick(k)->HasFlag(AK_ISNICK) ? "NICK " : "MASK ") + << (ci->GetAkick(k)->HasFlag(AK_ISNICK) ? ci->GetAkick(k)->nc->display : ci->GetAkick(k)->mask) + << " " << ci->GetAkick(k)->creator << " " << ci->GetAkick(k)->addtime << " :"; + if (!ci->GetAkick(k)->reason.empty()) + db << ci->GetAkick(k)->reason; + db << endl; + } + if (ci->GetMLockCount(true)) + { + db << "MD MLOCK_ON"; + for (j = 0; ChannelModes[j].Value != -1; ++j) + if (ci->HasMLock(ChannelModes[j].Value, true)) + db << " " << ChannelModes[j].Name; + db << endl; + } + if (ci->GetMLockCount(false)) + { + db << "MD MLOCK_OFF"; + for (j = 0; ChannelModes[j].Value != -1; ++j) + if (ci->HasMLock(ChannelModes[j].Value, false)) + db << " " << ChannelModes[j].Name; + db << endl; + } + std::string Param; + for (j = 0; ChannelModes[j].Value != -1; ++j) + if (ci->GetParam(ChannelModes[j].Value, &Param)) + db << "MD MLP " << ChannelModes[j].Name << " " << Param; + if (!ci->memos.memos.empty()) + { + MemoInfo *memos = &ci->memos; + + for (k = 0; k < memos->memos.size(); ++k) + { + db << "MD MI " << memos->memos[k]->number << " " << memos->memos[k]->time << " " << memos->memos[k]->sender; + if (memos->memos[k]->HasFlag(MF_UNREAD)) + db << " UNREAD"; + if (memos->memos[k]->HasFlag(MF_RECEIPT)) + db << " RECEIPT"; + if (memos->memos[k]->HasFlag(MF_NOTIFYS)) + db << " NOTIFYS"; + db << " :" << memos->memos[k]->text << endl; + } + } + if (ci->entry_message) + db << "MD ENTRYMSG :" << ci->entry_message << endl; + if (ci->bi) + db << "MD BI NAME " << ci->bi->nick << endl; + if (ci->botflags.FlagCount()) + { + db << "MD BI FLAGS"; + for (j = 0; BotFlags[j].Flag != -1; ++j) + if (ci->botflags.HasFlag(BotFlags[j].Flag)) + db << " " << BotFlags[j].Name; + db << endl; + } + db << "MD BI TTB BOLDS " << ci->ttb[0] << " COLORS " << ci->ttb[1] << " REVERSES " << ci->ttb[2] << " UNDERLINES " << ci->ttb[3] << " BADWORDS " << ci->ttb[4] << " CAPS " << ci->ttb[5] << " FLOOD " << ci->ttb[6] << " REPEAT " << ci->ttb[7] << endl; + if (ci->capsmin) + db << "MD BI CAPSMINS " << ci->capsmin << endl; + if (ci->capspercent) + db << "MD BI CAPSPERCENT " << ci->capspercent << endl; + if (ci->floodlines) + db << "MD BI FLOODLINES " << ci->floodlines << endl; + if (ci->floodsecs) + db << "MD BI FLOODSECS " << ci->floodsecs << endl; + if (ci->repeattimes) + db << "MD BI REPEATTIMES " << ci->repeattimes << endl; + for (k = 0; k < ci->GetBadWordCount(); ++k) + db << "MD BI BADWORD " + << (ci->GetBadWord(k)->type == BW_ANY ? "ANY " : "") + << (ci->GetBadWord(k)->type == BW_SINGLE ? "SINGLE " : "") + << (ci->GetBadWord(k)->type == BW_START ? "START " : "") + << (ci->GetBadWord(k)->type == BW_END ? "END " : "") + << ":" << ci->GetBadWord(k)->word << endl; + + FOREACH_MOD(I_OnDatabaseWriteMetadata, OnDatabaseWriteMetadata(WriteMetadata, ci)); + } + } + + HostCore *hc; + for (hc = hostCoreListHead(); hc; hc = hc->next) + { + db << "HI " << hc->nick << " " << hc->creator << " " << hc->time << " " << hc->vHost << " :" << hc->vIdent << endl; + } + + for (i = 0; i < akills.count; ++i) + { + Akill *ak = static_cast(akills.list[i]); + db << "OS AKILL " << ak->user << " " << ak->host << " " << ak->by << " " << ak->seton << " " << ak->expires << " :" << ak->reason << endl; + } + + db << "OS STATS " << maxusercnt << " " << maxusertime << endl; + SXLine *sx; + for (i = 0; i < sglines.count; ++i) + { + sx = static_cast(sglines.list[i]); + db << "OS SGLINE " << sx->mask << " " << sx->by << " " << sx->seton << " " << sx->expires << " :" << sx->reason << endl; + } + for (i = 0; i < sqlines.count; ++i) + { + sx = static_cast(sqlines.list[i]); + db << "OS SQLINE " << sx->mask << " " << sx->by << " " << sx->seton << " " << sx->expires << " :" << sx->reason << endl; + } + for (i = 0; i < szlines.count; ++i) + { + sx = static_cast(szlines.list[i]); + db << "OS SZLINE " << sx->mask << " " << sx->by << " " << sx->seton << " " << sx->expires << " :" << sx->reason << endl; + } + + for (i = 0; i < nexceptions; i++) + { + db << "OS EXCEPTION " << exceptions[i].mask << " " << exceptions[i].limit << " " << exceptions[i].who << " " << exceptions[i].time << " " << exceptions[i].expires << " " << exceptions[i].reason << endl; + } + + FOREACH_MOD(I_OnDatabaseWrite, OnDatabaseWrite(Write)); + + db.close(); + + return EVENT_CONTINUE; + } + + void OnModuleLoad(User *u, Module *m) + { + if (!u) + return; + + Implementation events[] = { I_OnDatabaseRead, I_OnDatabaseReadMetadata }; + for (int i = 0; i < 2; ++i) + { + std::vector::iterator it = std::find(ModuleManager::EventHandlers[events[i]].begin(), ModuleManager::EventHandlers[events[i]].end(), m); + /* This module wants to read from the database */ + if (it != ModuleManager::EventHandlers[events[i]].end()) + { + /* Loop over the database and call the events it would on a normal startup */ + ReadDatabase(m); + } + } + } +}; + +MODULE_INIT(DBPlain) + diff --git a/src/core/ns_forbid.c b/src/core/ns_forbid.c index f45dadceb..c58dda380 100644 --- a/src/core/ns_forbid.c +++ b/src/core/ns_forbid.c @@ -50,7 +50,9 @@ class CommandNSForbid : public Command } delete na; } - na = new NickAlias(nick, new NickCore(nick)); + NickCore *nc = new NickCore(nick); + nc->SetFlag(NI_FORBIDDEN); + na = new NickAlias(nick, nc); if (na) { na->SetFlag(NS_FORBIDDEN); diff --git a/src/core/ns_identify.c b/src/core/ns_identify.c index af111689c..dc9e6a59e 100644 --- a/src/core/ns_identify.c +++ b/src/core/ns_identify.c @@ -65,7 +65,6 @@ class CommandNSIdentify : public Command alog("%s: %s!%s@%s logged out of account %s", Config.s_NickServ, u->nick, u->GetIdent().c_str(), u->host, u->nc->display); } - u->UpdateHost(); if (na->last_realname) delete [] na->last_realname; na->last_realname = sstrdup(u->realname); @@ -75,6 +74,8 @@ class CommandNSIdentify : public Command ircdproto->SendAccountLogin(u, u->nc); ircdproto->SetAutoIdentificationToken(u); + u->UpdateHost(); + FOREACH_MOD(I_OnNickIdentify, OnNickIdentify(u)); alog("%s: %s!%s@%s identified for nick %s", Config.s_NickServ, u->nick, u->GetIdent().c_str(), u->host, u->nick); diff --git a/src/core/os_ignore.c b/src/core/os_ignore.c index bd7487f36..e7bb27a52 100644 --- a/src/core/os_ignore.c +++ b/src/core/os_ignore.c @@ -140,12 +140,68 @@ class OSIgnore : public Module this->SetType(CORE); this->AddCommand(OPERSERV, new CommandOSIgnore()); - ModuleManager::Attach(I_OnOperServHelp, this); + Implementation i[] = { I_OnOperServHelp, I_OnDatabaseRead, I_OnDatabaseWrite }; + ModuleManager::Attach(i, this, 3); } + void OnOperServHelp(User *u) { notice_lang(Config.s_OperServ, u, OPER_HELP_CMD_IGNORE); } + + EventReturn OnDatabaseRead(const std::vector ¶ms) + { + std::string buf; + + if (params[0] == "OS" && params.size() >= 4 && params[1] == "IGNORE") + { + IgnoreData *ign = new IgnoreData; + ign->mask = sstrdup(params[2].c_str()); + ign->time = strtol(params[3].c_str(), NULL, 10); + ign->prev = NULL; + ign->next = ignore; + if (ignore) + ignore->prev = ign; + ignore = ign; + + return EVENT_STOP; + } + + return EVENT_CONTINUE; + } + + void OnDatabaseWrite(void (*Write)(const std::string &)) + { + IgnoreData *ign, *next; + time_t now = time(NULL); + + for (ign = ignore; ign; ign = next) + { + next = ign->next; + + if (ign->time != 0 && ign->time <= now) + { + if (debug) +