/* +------------------------------------+ * | Inspire Internet Relay Chat Daemon | * +------------------------------------+ * * InspIRCd: (C) 2002-2011 InspIRCd Development Team * See: http://wiki.inspircd.org/Credits * * This program is free but copyrighted software; see * the file COPYING for details. * * --------------------------------------------------- */ #include "inspircd.h" #include "account.h" #include "hash.h" /* $ModDesc: Read and write accounts from a flat-file database */ static dynamic_reference account("account"); static dynamic_reference db("accountdb"); /****************************************************************************** * Flat-file database read/write ******************************************************************************/ /** reads the account database and returns structure with it */ class DatabaseReader { /* file */ FILE *fd; public: /* constructor, opens the file */ DatabaseReader (std::string filename) { /* initialize */ fd = NULL; /* open the file */ fd = fopen (filename.c_str ( ), "r"); /* if we can't open the file, return. */ if (!fd) return; } /* destructor will close the file */ ~DatabaseReader ( ) { /* if fd is not null, close it */ if (fd) fclose (fd); } /* read and store next entry */ bool next ( ) { /* if fd is NULL, fake eof */ if (!fd) return false; std::string str; /* read single characters from the file and add them to the string, end at EOF or \n, ignore \r */ while (1) { int c = fgetc (fd); if ((c == EOF) || ((unsigned char)c == '\n')) break; if ((unsigned char)c == '\r') continue; str.push_back ((unsigned char)c); } /* ready to parse the line */ if (str == "") return false; irc::string name; time_t ts, hash_password_ts; std::string hash, password; std::map extensions; std::string token; irc::spacesepstream sep(str, false); /* get first one */ /* malformed if it is not acctinfo */ if (!sep.GetToken (token) || token != "acctinfo") { ServerInstance->Logs->Log ("MODULE", DEFAULT, "malformed account database - entry didn't start with acctinfo"); return false; } /* read account name */ /* if we don't have one, database is malformed */ if (!sep.GetToken (name)) { ServerInstance->Logs->Log ("MODULE", DEFAULT, "malformed account database - ran out of tokens, expected name"); return false; } /* read account TS */ /* if we don't have one, database is malformed */ if (!sep.GetToken (token)) { ServerInstance->Logs->Log ("MODULE", DEFAULT, "malformed account database - ran out of tokens, expected ts"); return false; } ts = atol(token.c_str()); /* read hash type */ /* if we don't have one, database is malformed */ if (!sep.GetToken (hash)) { ServerInstance->Logs->Log ("MODULE", DEFAULT, "malformed account database - ran out of tokens, expected hash"); return false; } /* read password */ /* if we don't have one, database is malformed */ if (!sep.GetToken (password)) { ServerInstance->Logs->Log ("MODULE", DEFAULT, "malformed account database - ran out of tokens, expected password"); return false; } /* read hash and password TS */ /* if we don't have one, database is malformed */ if (!sep.GetToken (token)) { ServerInstance->Logs->Log ("MODULE", DEFAULT, "malformed account database - ran out of tokens, expected hash/password ts"); return false; } hash_password_ts = atol(token.c_str()); /* initial entry read, read next lines in a loop until end, eof means malformed database again */ while (1) { str.clear ( ); /* read the line */ while (1) { int c = fgetc (fd); if (c == EOF) { ServerInstance->Logs->Log ("MODULE", DEFAULT, "malformed account database - eof, expected end"); return false; } unsigned char c2 = (unsigned char)c; if (c2 == '\n') break; if (c2 == '\r') continue; str.push_back (c2); } irc::spacesepstream sep2(str, false); /* get the token */ if (str == "") { ServerInstance->Logs->Log ("MODULE", DEFAULT, "malformed account database - empty line"); return false; } sep2.GetToken (token); /* break the loop if token is "end" */ if (token == "end") break; extensions.insert(std::make_pair(token, sep2.GetRemaining())); } AccountDBEntry* entry = db->GetAccount(name, false); if(!entry || entry->ts > ts) { if(entry) db->RemoveAccount(false, entry); entry = db->AddAccount(false, name, ts, hash, password, hash_password_ts); } else if(entry->ts == ts) { if(hash_password_ts > entry->hash_password_ts) { entry->hash = hash; entry->password = password; entry->hash_password_ts = hash_password_ts; } } else return true; for(std::map::iterator i = extensions.begin(); i != extensions.end(); ++i) { ExtensionItem* ext = ServerInstance->Extensions.GetItem(i->first); if (ext) ext->unserialize(FORMAT_PERSIST, entry, i->second); } db->SendAccount(entry); return true; } }; /* class being a database writer, gets a database file name on construct */ class DatabaseWriter { /* file stream */ FILE *fd; std::string dbname, tmpname; /* public */ public: /* constructor */ DatabaseWriter (std::string filename) { fd = NULL; dbname = filename; tmpname = filename + ".tmp"; /* ready, open temporary database */ fd = fopen (tmpname.c_str ( ), "w"); if (!fd) { ServerInstance->Logs->Log ("MODULE", DEFAULT, "cannot save to the account database"); } } /* destructor */ ~DatabaseWriter ( ) { if (fd) { /* saving has been ended, close the database and flush buffers */ fclose (fd); /* rename the database file */ if (rename (tmpname.c_str ( ), dbname.c_str ( )) == -1) { ServerInstance->Logs->Log ("MODULE", DEFAULT, "can't rename the database file"); } } } /* save the single entry */ void next (const AccountDBEntry* ent) { if (!fd) return; /* first, construct the acctinfo line */ std::string line; line.append("acctinfo ").append (ent->name).append (" ").append (ConvToStr(ent->ts)).append (" ") .append (ent->hash).append (" ").append (ent->password).append (" ") .append (ConvToStr(ent->hash_password_ts)).append ("\n"); if (fputs (line.c_str ( ), fd) == EOF) { ServerInstance->Logs->Log ("MODULE", DEFAULT, "unable to write the account entry"); fclose (fd); fd = NULL; return; } for(Extensible::ExtensibleStore::const_iterator i = ent->GetExtList().begin(); i != ent->GetExtList().end(); ++i) { ExtensionItem* item = i->first; std::string value = item->serialize(FORMAT_PERSIST, ent, i->second); if (!value.empty()) { std::string toWrite = item->name + " " + value + "\n"; if (fputs (toWrite.c_str ( ), fd) == EOF) { ServerInstance->Logs->Log ("MODULE", DEFAULT, "can't write extension item entry"); fclose (fd); fd = NULL; return; } } } if (fputs ("end\n", fd) == EOF) { ServerInstance->Logs->Log ("MODULE", DEFAULT, "can't write end of account entry"); fclose (fd); fd = NULL; return; } } }; class ModuleFlatfileAccount : public Module { private: std::string dbfile; bool dirty; // dbfile needs to be flushed to disk void WriteFileDatabase ( ) { // Dump entire database; open/close in constructor/destructor DatabaseWriter dbwriter (dbfile); for (AccountDB::const_iterator i = db->GetDB().begin(); i != db->GetDB().end(); ++i) dbwriter.next(i->second); } public: ModuleFlatfileAccount() : dirty(true) { } void init() { if(!db) throw ModuleException("m_flatfile_account requires that m_account be loaded"); Implementation eventlist[] = { I_OnBackgroundTimer, I_OnEvent }; ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); for(DatabaseReader dbreader(dbfile);dbreader.next();) {} } void ReadConfig(ConfigReadStatus&) { ConfigTag* conf = ServerInstance->Config->GetTag("flatfileacct"); dbfile = conf->getString("dbfile"); } /* called on background timer to write all accounts to disk if they were changed */ void OnBackgroundTimer (time_t cur) { /* if not dirty then don't do anything */ if (!dirty) return; /* dirty, an account was changed, save it */ if(db) WriteFileDatabase(); /* clear dirty to prevent next savings */ dirty = false; } void OnEvent(Event& event) { if(event.id == "accountdb_modified") dirty = true; } void Prioritize() { // database reading may depend on extension item providers being loaded ServerInstance->Modules->SetPriority(this, I_ModuleInit, PRIORITY_LAST); } Version GetVersion() { return Version("Read and write accounts from a flat-file database", VF_VENDOR); } }; MODULE_INIT(ModuleFlatfileAccount)