/** * flatfile.C: This file contains the implementation of the Atheme 0.1 flatfile database format, with metadata extensions. * * Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team * Rights to this code are as documented in COPYING. * * * Portions of this file were derived from sources bearing the following license: * Copyright © 2005-2006 Atheme Development Group * Rights to this code are as documented in doc/pod/license.pod. * * $Id: flatfile.C,v 1.11 2007/09/22 14:27:26 pippijn Exp $ */ #include #include "atheme.h" #include #include #include #include #include #include #include #include #include #include #include static char const rcsid[] = "$Id: flatfile.C,v 1.11 2007/09/22 14:27:26 pippijn Exp $"; /* database versions */ #define DB_SHRIKE 1 #define DB_ATHEME 2 namespace database { /* flatfile state */ unsigned int muout = 0, mcout = 0, caout = 0, kout = 0; struct flatfile : handler { virtual void save (); virtual void load (); }; struct flatfile_save_myusers_cb { flatfile_save_myusers_cb (FILE *file) : f (file) { } void operator () (myuser_t::pair_type &it) { myuser_t *mu = it.second; metadata::map_type::iterator mdit, mdet; myuser_t::memo_vector::iterator mzit, mzet; myuser_t::mzignore_vector::iterator miit, miet; node_t *tn; /* MU [lastlogin] [failnum*] [lastfail*] * [lastfailon*] [flags] * * * failnum, lastfail, and lastfailon are deprecated (moved to metadata) */ fprintf (f, "MU %s %s %s %ld", mu->name, mu->pass, mu->email, mu->registered); fprintf (f, " %ld", mu->lastlogin); fprintf (f, " 0 0 0"); if (!mu->logins.empty ()) fprintf (f, " %d\n", mu->flags & ~MU_NOBURSTLOGIN); else fprintf (f, " %d\n", mu->flags); muout++; for (mdit = mu->mdmap.begin (), mdet = mu->mdmap.end (); mdit != mdet; ++mdit) { metadata *md = mdit->second; fprintf (f, "MD U %s %s %s\n", mu->name, md->key, md->value); } for (mzit = mu->memos.begin (), mzet = mu->memos.end (); mzit != mzet; ++mzit) { mymemo_t *mz = *mzit; fprintf (f, "ME %s %s %lu %u %s\n", mu->name, mz->sender, mz->sent, mz->status, mz->text); } for (miit = mu->memo_ignores.begin (), miet = mu->memo_ignores.end (); miit != miet; ++miit) fprintf (f, "MI %s %s\n", mu->name, *miit); LIST_FOREACH (tn, mu->access_list.head) { fprintf (f, "AC %s %s\n", mu->name, (char *) tn->data); } LIST_FOREACH (tn, mu->nicks.head) { mynick_t *mn = static_cast (tn->data); fprintf (f, "MN %s %s %ld %ld\n", mu->name, mn->nick, (long) mn->registered, (long) mn->lastseen); } } private: FILE *f; }; /* write db */ void flatfile::save () { myuser_t *mu; mychan_t *mc; chanacs_t *ca; kline_t *k; svsignore_t *svsignore; node_t *n, *tn; FILE *f; int errno1, was_errored = 0; svsignore_vector::iterator siit, siet; kline_t::list_type::iterator klit, klet; metadata::map_type::iterator mdit, mdet; errno = 0; /* reset state */ muout = 0, mcout = 0, caout = 0, kout = 0; /* write to a temporary file first */ if (!(f = fopen (DATADIR "/" PACKAGE_NAME ".db.new", "w"))) { errno1 = errno; slog (LG_ERROR, "flatfile::save(): cannot create " PACKAGE_NAME ".db.new: %s", strerror (errno1)); wallops (_("\2DATABASE ERROR\2: flatfile::save(): cannot create %s.db.new: %s"), PACKAGE_NAME, strerror (errno1)); snoop (_("\2DATABASE ERROR\2: flatfile::save(): cannot create %s.db.new: %s"), PACKAGE_NAME, strerror (errno1)); return; } /* write the database version */ fprintf (f, "DBV 6\n"); fprintf (f, "CF %s\n", bitmask_to_flags (ca_all, chanacs_flags)); slog (LG_DEBUG, "flatfile::save(): saving myusers"); std::for_each (myuser_t::map.begin (), myuser_t::map.end (), flatfile_save_myusers_cb (f)); slog (LG_DEBUG, "flatfile::save(): saving mychans"); foreach (mychan_t::pair_type &mp, mychan_t::map) { mc = mp.second; /* find a founder */ mu = NULL; LIST_FOREACH (tn, mc->chanacs.head) { ca = static_cast (tn->data); if (ca->myuser != NULL && ca->level & CA_FOUNDER) { mu = ca->myuser; break; } } /* MC [used] [flags] * [mlock_on] [mlock_off] [mlock_limit] [mlock_key] * PASS is now ignored -- replaced with a "0" to avoid having to special-case this version */ fprintf (f, "MC %s %s %s %ld %ld", mc->name, "0", mu != NULL ? mu->name : "*", (long)mc->registered, (long)mc->used); fprintf (f, " %d", mc->flags); fprintf (f, " %d", mc->mlock_on); fprintf (f, " %d", mc->mlock_off); fprintf (f, " %d", mc->mlock_limit); if (mc->mlock_key) fprintf (f, " %s\n", mc->mlock_key); else fprintf (f, "\n"); mcout++; LIST_FOREACH (tn, mc->chanacs.head) { ca = (chanacs_t *) tn->data; fprintf (f, "CA %s %s %s %ld\n", ca->mychan->name, ca->myuser ? ca->myuser->name : ca->host, bitmask_to_flags (ca->level, chanacs_flags), ca->ts); for (mdit = ca->mdmap.begin (), mdet = ca->mdmap.end (); mdit != mdet; ++mdit) { metadata *md = mdit->second; fprintf (f, "MD A %s:%s %s %s\n", ca->mychan->name, (ca->myuser) ? ca->myuser->name : ca->host, md->key, md->value); } caout++; } for (mdit = mc->mdmap.begin (), mdet = mc->mdmap.end (); mdit != mdet; ++mdit) { metadata *md = mdit->second; fprintf (f, "MD C %s %s %s\n", mc->name, md->key, md->value); } } /* subscriptions -- we have to walk the myuser table again because otherwise we could encounter unknowns on restart. */ slog (LG_DEBUG, "flatfile::save(): saving subscriptions"); myuser_t::map_type::iterator muit = myuser_t::map.begin (); myuser_t::map_type::iterator muet = myuser_t::map.end (); while (muit != muet) { mu = muit->second; metadata_subscription_t *subscriptor; LIST_FOREACH (n, mu->subscriptions.head) { node_t *i; dynstr str (64); subscriptor = (metadata_subscription_t *) n->data; LIST_FOREACH (i, subscriptor->taglist.head) { str.add (static_cast (i->data), strlen (static_cast (i->data))); if (i->next) str.add (','); } fprintf (f, "SU %s %s %s\n", mu->name, subscriptor->mu->name, str.c_str ()); } ++muit; } /* Services ignores */ slog (LG_DEBUG, "flatfile::save(): saving svsignores"); for (siit = svs_ignore_list.begin (), siet = svs_ignore_list.end (); siit != siet; ++siit) { svsignore = *siit; /* SI */ fprintf (f, "SI %s %ld %s %s\n", svsignore->mask, (long) svsignore->settime, svsignore->setby, svsignore->reason); } /* Services operators */ slog (LG_DEBUG, "flatfile::save(): saving sopers"); foreach (soper_t *soper, soper_t::list) { if (soper->flags & SOPER_CONF || soper->myuser == NULL) continue; /* SO */ fprintf (f, "SO %s %s %d\n", soper->myuser->name, soper->classname, soper->flags); } slog (LG_DEBUG, "flatfile::save(): saving klines"); for (klit = kline_t::list.begin (), klet = kline_t::list.end (); klit != klet; ++klit) { k = *klit; /* KL */ fprintf (f, "KL %s %s %ld %ld %s %s\n", k->user, k->host, k->duration, (long) k->settime, k->setby, k->reason); kout++; } /* DE */ fprintf (f, "DE %d %d %d %d\n", muout, mcout, caout, kout); was_errored = ferror (f); was_errored |= fclose (f); if (was_errored) { errno1 = errno; slog (LG_ERROR, "flatfile::save(): cannot write to " PACKAGE_NAME ".db.new: %s", strerror (errno1)); wallops (_("\2DATABASE ERROR\2: flatfile::save(): cannot write to %s.db.new: %s"), PACKAGE_NAME, strerror (errno1)); snoop (_("\2DATABASE ERROR\2: flatfile::save(): cannot write to %s.db.new: %s"), PACKAGE_NAME, strerror (errno1)); return; } /* now, replace the old database with the new one, using an atomic rename */ unlink (DATADIR "/" PACKAGE_NAME ".db"); if ((rename (DATADIR "/" PACKAGE_NAME ".db.new", DATADIR "/" PACKAGE_NAME ".db")) < 0) { errno1 = errno; slog (LG_ERROR, "flatfile::save(): cannot rename " PACKAGE_NAME ".db.new to " PACKAGE_NAME ".db: %s", strerror (errno1)); wallops (_("\2DATABASE ERROR\2: flatfile::save(): cannot rename %s.db.new to %s.db: %s"), PACKAGE_NAME, strerror (errno1)); snoop (_("\2DATABASE ERROR\2: flatfile::save(): cannot rename %s.db.new to %s.db: %s"), PACKAGE_NAME, strerror (errno1)); return; } } /* loads db */ void flatfile::load () { myuser_t *mu, *founder = NULL; mychan_t *mc; kline_t *k; svsignore_t *svsignore; unsigned int i = 0, linecnt = 0, muin = 0, mcin = 0, cain = 0, kin = 0; FILE *f; char *item, *s, dBuf[BUFSIZE]; unsigned int their_ca_all = ca_all; f = fopen (DATADIR "/" PACKAGE_NAME ".db", "r"); if (f == NULL) { if (errno == ENOENT) { slog (LG_ERROR, "flatfile::load(): %s does not exist, creating it", DATADIR "/" PACKAGE_NAME ".db"); return; } else { slog (LG_ERROR, "flatfile::load(): can't open %s for reading: %s", DATADIR "/" PACKAGE_NAME ".db", strerror (errno)); slog (LG_ERROR, "flatfile::load(): exiting to avoid data loss"); exit (1); } } slog (LG_DEBUG, "flatfile::load(): ----------------------- loading ------------------------"); /* start reading it, one line at a time */ while (fgets (dBuf, BUFSIZE, f)) { linecnt++; /* check for unimportant lines */ item = strtok (dBuf, " "); strip (item); if (*item == '#' || *item == '\n' || *item == '\t' || *item == ' ' || *item == '\0' || *item == '\r' || !*item) continue; /* database version */ if (!strcmp ("DBV", item)) { i = atoi (strtok (NULL, " ")); if (i > 6) { slog (LG_INFO, "flatfile::load(): database version is %d; I only understand " "6 (Ermyth 2.3), " "5 (Atheme 2.0, 2.1), " "4 (Atheme 0.2), " "3 (Atheme 0.2 without CA_ACLVIEW), " "2 (Atheme 0.1) or " "1 (Shrike)", i); exit (EXIT_FAILURE); } } /* enabled chanacs flags */ if (!strcmp ("CF", item)) { s = strtok (NULL, " "); if (s == NULL) slog (LG_INFO, "flatfile::load(): missing param to CF"); else { their_ca_all = flags_to_bitmask (s, chanacs_flags, 0); if (their_ca_all & ~ca_all) slog (LG_ERROR, "flatfile::load(): losing flags %s from file", bitmask_to_flags (their_ca_all & ~ca_all, chanacs_flags)); if (~their_ca_all & ca_all) slog (LG_ERROR, "flatfile::load(): making up flags %s not present in file", bitmask_to_flags (~their_ca_all & ca_all, chanacs_flags)); } } /* myusers */ if (!strcmp ("MU", item)) { char *muname, *mupass, *muemail; if ((s = strtok (NULL, " "))) { /* We need to know the flags before we myuser_t::create, * so we need a few temporary places to put stuff. */ unsigned int registered, lastlogin; char *failnum, *lastfailaddr, *lastfailtime; if ((mu = myuser_t::find (s))) continue; muin++; muname = s; mupass = strtok (NULL, " "); muemail = strtok (NULL, " "); registered = atoi (strtok (NULL, " ")); lastlogin = atoi (strtok (NULL, " ")); failnum = strtok (NULL, " "); lastfailaddr = strtok (NULL, " "); lastfailtime = strtok (NULL, " "); mu = myuser_t::create (muname, mupass, muemail, atol (strtok (NULL, " "))); mu->registered = registered; mu->lastlogin = lastlogin; if (strcmp (failnum, "0")) mu->add_metadata ("private:loginfail:failnum", failnum); if (strcmp (lastfailaddr, "0")) mu->add_metadata ("private:loginfail:lastfailaddr", lastfailaddr); if (strcmp (lastfailtime, "0")) mu->add_metadata ("private:loginfail:lastfailtime", lastfailtime); /* Verification keys were moved to metadata, * but we'll still accept them from legacy * databases. Note that we only transfer over * initial registration keys -- I don't think * we saved mu->temp, so we can't transition * e-mail change keys anyway. --alambert */ if ((s = strtok (NULL, " "))) { strip (s); mu->add_metadata ("private:verify:register:key", s); mu->add_metadata ("private:verify:register:timestamp", "0"); } } } if (!strcmp ("ME", item)) { char *sender, *text; time_t mtime; unsigned int status; mymemo_t *mz; mu = myuser_t::find (strtok (NULL, " ")); sender = strtok (NULL, " "); mtime = atoi (strtok (NULL, " ")); status = atoi (strtok (NULL, " ")); text = strtok (NULL, "\n"); if (!mu) { slog (LG_DEBUG, "flatfile::load(): memo for unknown account"); continue; } if (!sender || !mtime || !text) continue; mz = new mymemo_t; strlcpy (mz->sender, sender, NICKLEN); strlcpy (mz->text, text, MEMOLEN); mz->sent = mtime; mz->status = status; if (!(mz->status & MEMO_READ)) mu->memoct_new++; mu->memos.insert (mz); } if (!strcmp ("MI", item)) { char *user, *target; user = strtok (NULL, " "); target = strtok (NULL, "\n"); mu = myuser_t::find (user); if (!mu) { slog (LG_DEBUG, "flatfile::load(): invalid ignore (MI %s %s)", user, target); continue; } mu->memo_ignores.insert (sstrdup (target)); } /* myuser access list */ if (!strcmp ("AC", item)) { char *user, *mask; user = strtok (NULL, " "); mask = strtok (NULL, "\n"); mu = myuser_t::find (user); if (mu == NULL) { slog (LG_DEBUG, "flatfile::load(): invalid access entry<%s> for unknown user<%s>", mask, user); continue; } mu->access_add (mask); } /* registered nick */ if (!strcmp ("MN", item)) { char *user, *nick, *treg, *tseen; mynick_t *mn; user = strtok (NULL, " "); nick = strtok (NULL, " "); treg = strtok (NULL, " "); tseen = strtok (NULL, " "); mu = myuser_t::find (user); if (mu == NULL || nick == NULL || tseen == NULL) { slog (LG_DEBUG, "flatfile::load(): invalid registered nick<%s> for user<%s>", nick, user); continue; } mn = mynick_t::create (mu, nick); mn->registered = atoi (treg); mn->lastseen = atoi (tseen); } /* subscriptions */ if (!strcmp ("SU", item)) { char *user, *sub_user, *tags, *tag; myuser_t *subscriptor; metadata_subscription_t *md; user = strtok (NULL, " "); sub_user = strtok (NULL, " "); tags = strtok (NULL, "\n"); if (!user || !sub_user || !tags) { slog (LG_INFO, "flatfile::load(): invalid subscription (line %d)", linecnt); continue; } strip (tags); mu = myuser_t::find (user); subscriptor = myuser_t::find (sub_user); if (!mu || !subscriptor) { slog (LG_INFO, "flatfile::load(): invalid subscription <%s,%s> (line %d)", user, sub_user, linecnt); continue; } md = new metadata_subscription_t; md->mu = subscriptor; tag = strtok (tags, ","); do node_add (sstrdup (tag), node_create (), &md->taglist); while ((tag = strtok (NULL, ",")) != NULL); node_add (md, node_create (), &mu->subscriptions); } /* services oper */ if (!strcmp ("SO", item)) { char *user, *classname, *flagstr; user = strtok (NULL, " "); classname = strtok (NULL, " "); flagstr = strtok (NULL, "\n"); mu = myuser_t::find (user); if (!mu || !classname || !flagstr) { slog (LG_DEBUG, "flatfile::load(): invalid services oper (SO %s %s %s)", user, classname, flagstr); continue; } soper_add (mu->name, classname, atoi (flagstr) & ~SOPER_CONF); } /* mychans */ if (!strcmp ("MC", item)) { char *mcname, *mcpass; if ((s = strtok (NULL, " "))) { if ((mc = mychan_t::find (s))) continue; mcin++; mcname = s; /* unused */ mcpass = strtok (NULL, " "); mc = mychan_t::create (mcname); founder = myuser_t::find (strtok (NULL, " ")); mc->registered = atoi (strtok (NULL, " ")); mc->used = atoi (strtok (NULL, " ")); mc->flags = atoi (strtok (NULL, " ")); mc->mlock_on = atoi (strtok (NULL, " ")); mc->mlock_off = atoi (strtok (NULL, " ")); mc->mlock_limit = atoi (strtok (NULL, " ")); if ((s = strtok (NULL, " "))) { strip (s); if (*s != '\0' && *s != ':' && !strchr (s, ',')) mc->mlock_key = sstrdup (s); } if (i < 5 && config_options.join_chans) mc->flags |= MC_GUARD; } } /* Metadata entry */ if (!strcmp ("MD", item)) { char *type = strtok (NULL, " "); char *name = strtok (NULL, " "); char *property = strtok (NULL, " "); char *value = strtok (NULL, ""); if (!type || !name || !property || !value) continue; strip (value); if (*type == 'U') { mu = myuser_t::find (name); if (mu != NULL) mu->add_metadata (property, value); } else if (*type == 'C') { mc = mychan_t::find (name); if (mc != NULL) mc->add_metadata (property, value); } else if (*type == 'A') { chanacs_t *ca; char *mask; mask = strrchr (name, ':'); if (mask != NULL) { *mask++ = '\0'; ca = chanacs_find_by_mask (mychan_t::find (name), mask, CA_NONE); if (ca != NULL) ca->add_metadata (property, value); } } } /* Channel URLs */ if (!strcmp ("UR", item)) { char *chan, *url; chan = strtok (NULL, " "); url = strtok (NULL, " "); strip (url); if (chan && url) { mc = mychan_t::find (chan); if (mc) mc->add_metadata ("url", url); } } /* Channel entry messages */ if (!strcmp ("EM", item)) { char *chan, *message; chan = strtok (NULL, " "); message = strtok (NULL, ""); strip (message); if (chan && message) { mc = mychan_t::find (chan); if (mc) mc->add_metadata ("private:entrymsg", message); } } /* chanacs */ if (!strcmp ("CA", item)) { chanacs_t *ca; char *cachan, *causer; cachan = strtok (NULL, " "); causer = strtok (NULL, " "); if (cachan && causer) { mc = mychan_t::find (cachan); mu = myuser_t::find (causer); if (mc == NULL || (mu == NULL && !validhostmask (causer))) { slog (LG_ERROR, "flatfile::load(): invalid chanacs (line %d)", linecnt); continue; } cain++; if (i >= DB_ATHEME) { unsigned int fl = flags_to_bitmask (strtok (NULL, " "), chanacs_flags, 0x0); char const * const tsstr = strtok (NULL, " "); time_t ts = 0; /* Compatibility with oldworld databases. --nenolod */ /* arbitrary cutoff to avoid touching newer +voOt entries -- jilles */ if (fl == OLD_CA_AOP && i < 4) fl = CA_AOP_DEF; /* * If the database revision is version 6 or newer, CA entries are * timestamped... otherwise we use 0 as the last modified TS * --nenolod/jilles */ if (tsstr != NULL) ts = atoi (tsstr); /* previous to CA_ACLVIEW, everyone could view * access lists. If they aren't AKICKed, upgrade * them. This keeps us from breaking old XOPs. */ if (i < 4) if (!(fl & CA_AKICK)) fl |= CA_ACLVIEW; /* Grant +h if they have +o, * the file does not have +h enabled * and we currently have +h enabled. * This preserves AOP, SOP and +*. */ if (fl & CA_OP && !(their_ca_all & CA_HALFOP) && ca_all & CA_HALFOP) fl |= CA_HALFOP; /* Set new-style founder flag */ if (founder != NULL && mu == founder && !(their_ca_all & CA_FOUNDER)) fl |= CA_FOUNDER; if ((!mu) && (validhostmask (causer))) ca = chanacs_add_host (mc, causer, fl, ts); else ca = chanacs_add (mc, mu, fl, ts); } else if (i == DB_SHRIKE) /* DB_SHRIKE */ { unsigned int fl = atol (strtok (NULL, " ")); unsigned int fl2 = 0x0; time_t ts = NOW; switch (fl) { case SHRIKE_CA_VOP: fl2 = chansvs.ca_vop; case SHRIKE_CA_AOP: fl2 = chansvs.ca_aop; case SHRIKE_CA_SOP: fl2 = chansvs.ca_sop; case SHRIKE_CA_SUCCESSOR: fl2 = CA_SUCCESSOR_0; case SHRIKE_CA_FOUNDER: fl2 = CA_FOUNDER_0; } if ((!mu) && (validhostmask (causer))) ca = chanacs_add_host (mc, causer, fl2, ts); else ca = chanacs_add (mc, mu, fl2, ts); } } } /* Services ignores */ if (!strcmp ("SI", item)) { char *mask, *setby, *reason, *tmp; time_t settime; mask = strtok (NULL, " "); tmp = strtok (NULL, " "); settime = atol (tmp); setby = strtok (NULL, " "); reason = strtok (NULL, ""); strip (reason); svsignore = svsignore_add (mask, reason); svsignore->settime = settime; svsignore->setby = sstrdup (setby); } /* klines */ if (!strcmp ("KL", item)) { char *user, *host, *reason, *setby, *tmp; time_t settime; long duration; user = strtok (NULL, " "); host = strtok (NULL, " "); tmp = strtok (NULL, " "); duration = atol (tmp); tmp = strtok (NULL, " "); settime = atol (tmp); setby = strtok (NULL, " "); reason = strtok (NULL, ""); strip (reason); k = kline_t::create (user, host, reason, duration); k->settime = settime; /* XXX this is not nice, oh well -- jilles */ k->expires = k->settime + k->duration; k->setby = sstrdup (setby); kin++; } /* end */ if (!strcmp ("DE", item)) { i = atoi (strtok (NULL, " ")); if (i != muin) slog (LG_ERROR, "flatfile::load(): got %d myusers; expected %d", muin, i); i = atoi (strtok (NULL, " ")); if (i != mcin) slog (LG_ERROR, "flatfile::load(): got %d mychans; expected %d", mcin, i); i = atoi (strtok (NULL, " ")); if (i != cain) slog (LG_ERROR, "flatfile::load(): got %d chanacs; expected %d", cain, i); if ((s = strtok (NULL, " "))) if ((i = atoi (s)) != kin) slog (LG_ERROR, "flatfile::load(): got %d klines; expected %d", kin, i); } } fclose (f); slog (LG_DEBUG, "flatfile::load(): ------------------------- done -------------------------"); } } // namespace database #define FACREG_TYPE database::flatfile #define FACREG_TYPE_NAME "flatfile" #define FACREG_INTERFACE_TYPE database::handler #include