Main Page | Modules | Namespace List | Class Hierarchy | Class List | File List | Class Members | File Members | Related Pages

db.c

Go to the documentation of this file.
00001 
00014 /*
00015  * Copyright (c) 1996-1997 Chip Norkus
00016  * Copyright (c) 1997 Max Byrd
00017  * Copyright (c) 1997 Greg Poma
00018  * Copyright (c) 1999-2000 James Hess
00019  * All rights reserved.
00020  *
00021  * Redistribution and use in source and binary forms, with or without
00022  * modification, are permitted provided that the following conditions
00023  * are met:
00024  * 1. Redistributions of source code must retain the above copyright
00025  *    notice, this list of conditions and the following disclaimer.
00026  * 2. Redistributions in binary form must reproduce the above copyright
00027  *    notice, this list of conditions and the following disclaimer in the
00028  *    documentation and/or other materials provided with the distribution.
00029  * 3. Neither the name of the authors nor the names of its contributors
00030  *    may be used to endorse or promote products derived from this software
00031  *    without specific prior written permission.
00032  *
00033  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
00034  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00035  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00036  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
00037  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
00038  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
00039  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00040  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00041  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
00042  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
00043  * SUCH DAMAGE.
00044  */
00045 
00046 #include "services.h"
00047 #include "nickserv.h"
00048 #include "chanserv.h"
00049 #include "db.h"
00050 #include "hash/md5pw.h"
00051 #include "memoserv.h"
00052 #include "macro.h"
00053 #include "hash.h"
00054 #include "queue.h"
00055 #include "log.h"
00056 #include "infoserv.h"
00057 
00058 
00059 #define NS_DIR "nickserv/"
00060 #define CS_DIR "chanserv/"
00061 #define OS_DIR "operserv/"
00062 #define MS_DIR "memoserv/"
00063 #define IS_DIR "infoserv/"
00064 
00065 #define NS_DB  NS_DIR"nickserv.db"
00066 #define CS_DB  CS_DIR"chanserv.db"
00067 #define MS_DB  MS_DIR"memoserv.db"
00068 #define TRG_DB OS_DIR"trigger.db"
00069 #define AKI_DB OS_DIR"akill.db"
00070 #define IS_DB  IS_DIR"infoserv.db"
00071 
00072 extern RegId top_regnick_idnum;
00073 
00074 #ifdef REQ_EMAIL
00075 # error REQ_EMAIL: does not work
00076 #endif
00077 
00078 static char cryptstr[PASSLEN + 2];  
00085 static char dbLine[2048];       
00091 parse_t state;                  /* yes, a global. */
00092 
00093 void AppendBuffer(char **p, const char *);
00094 char *str_dup(const char*);
00095 
00101 void unexpected_eof( const char* file_name )
00102 {
00103     fprintf(stderr, "Unexpected EOF: %s\n", file_name);
00104     logDump(corelog, "Unexpected EOF: %s", file_name);
00105     sshutdown(-1);
00106 }
00107         
00108 
00113 static char *dbReadString(FILE *fp)
00114 {
00115     char tempLine[125], *p = NULL, *y;
00116     int fin = 0, firstLn = 0;
00117 
00118     while(!fin && sfgets(tempLine, 124, db.is)) {
00119         y = strrchr(tempLine, '~');
00120         assert(y != NULL);
00121         *y = '\0';
00122 
00123         if (y[1] == '$')
00124             fin = 1;
00125 
00126         strcat(tempLine, "\n");
00127         if (*tempLine != '\n')
00128             AppendBuffer(&p, tempLine);
00129         firstLn = 0;
00130         *y = '~';
00131     }
00132     if (p && *p)
00133         p[strlen(p) - 1] = '\0';
00134 
00135     return p;
00136 }
00137 
00138 
00144 static void dbWriteString(FILE *fp, const char *istr)
00145 {
00146     char *p, *str;
00147     parse_t lineSplit;
00148 
00149     str = str_dup(istr);
00150     assert(parse_init(&lineSplit, str) == 0);
00151     lineSplit.delim = '\n';
00152 
00153     while((p = parse_getarg(&lineSplit))) {
00154         fprintf(fp, "%s~\n", p);
00155     }
00156     fprintf(fp, "~$\n");
00157     parse_cleanup(&lineSplit);
00158     FREE(str);
00159 }
00160 
00164 void saveNickData(void)
00165 {
00166     RegNickList *nl;
00167     nAccessList *accitem;
00168     int x = 0, bucket;
00169 
00170     db.ns = fopen(NS_DB, "w");
00171 
00172     if (db.ns == NULL) {
00173         sSend(":%s GOPER :ERROR: Unable to save NickServ database fopen failed: %s", myname, strerror(errno));
00174         logDump(corelog, "Unable to save NickServ database: fopen: %s", strerror(errno));
00175         return;
00176     }
00177 
00178     fprintf(db.ns, "version 3\n");
00179 
00180     for (bucket = 0; bucket < NICKHASHSIZE; bucket++) {
00181         nl = LIST_FIRST(&RegNickHash[bucket]);
00182         while (nl != NULL) {
00183             /* New nick insertion point */
00184             fprintf(db.ns, "nick ");
00185             if (nl->user[0] == '\0')
00186                 fprintf(db.ns, "%s . %s ", nl->nick, nl->host);
00187             else
00188                 fprintf(db.ns, "%s %s %s ", nl->nick, nl->user, nl->host);
00189 
00190             if (!(nl->flags & NENCRYPT))
00191             {
00192                 strcpy(cryptstr, (char *)nl->password);
00193                 xorit(cryptstr);
00194                 fprintf(db.ns, "@%s ", cryptstr);
00195             }
00196             else {
00197                 unsigned char *p;
00198 
00199                 // XXX Put note that PASSLEN must be at least 15
00200                 p = toBase64(nl->password, 16);
00201                 fprintf(db.ns, "$%s ", p);
00202                 FREE(p);
00203             }
00204 
00205             fprintf(db.ns, "%lu ", (unsigned long)nl->timestamp);
00206             fprintf(db.ns, "%lu ", (unsigned long)nl->timereg);
00207             fprintf(db.ns, "%i %X*%X\n", nl->flags, nl->regnum.a, nl->regnum.b);
00208 
00209             /* Infoserv data */
00210             fprintf(db.ns, "is %ld\n", nl->is_readtime);
00211 
00212 #ifdef REQ_EMAIL
00213             if (!(nl->flags & NACTIVE) || (nl->flags & NDEACC))
00214                 fprintf(db.ns, "akey %lu\n", nl->email_key);
00215 #endif
00216             if (nl->chpw_key)
00217                 fprintf(db.ns, "chkey %X\n", nl->chpw_key);
00218             if (nl->url != NULL)
00219                 fprintf(db.ns, "url %s %s\n", nl->nick, nl->url);
00220 #ifdef TRACK_GECOS
00221             if (nl->gecos != NULL)
00222                 fprintf(db.ns, "gecos :%s\n", nl->gecos);
00223 #endif
00224 
00225             if (nl->email[0] && strcmp(nl->email, "(none)"))
00226                 fprintf(db.ns, "email %s %s\n", nl->nick, nl->email);
00227 
00228             if ((nl->opflags & ~(OROOT | OSERVOP)))
00229                 fprintf(db.ns, "oper %s %lu 0 0\n", nl->nick, nl->opflags);
00230 
00231             if ((nl->flags & NMARK) && nl->markby)
00232                 fprintf(db.ns, "markby %s %s\n", nl->nick, nl->markby);
00233 
00234             if (nl->idtime != DEF_NDELAY)
00235                 fprintf(db.ns, "idtime %s %i\n", nl->nick, nl->idtime);
00236 
00237             for (accitem = LIST_FIRST(&nl->acl); accitem;
00238                  accitem = LIST_NEXT(accitem, al_lst))
00239                 fprintf(db.ns, "access %s %s\n", nl->nick, accitem->mask);
00240             x++;
00241             fflush(db.ns);
00242 #ifdef DBDEBUG
00243             sSend(":%s PRIVMSG " DEBUGCHAN " :Saved nick data (%s)",
00244                   NickServ, nl->nick);
00245 #endif
00246             nl = LIST_NEXT(nl, rn_lst);
00247         }
00248     }
00249     fprintf(db.ns, "done\n");
00250     fflush(db.ns);
00251     fclose(db.ns);
00252 #ifdef GLOBOPS_TIMED_MSGS
00253     sSend(":%s GLOBOPS :Completed save(%i)", NickServ, x);
00254 #else
00255     sSend(":%s PRIVMSG " LOGCHAN " :Completed save(%i)", NickServ, x);
00256 #endif
00257 }
00258 
00262 void readNickData()
00263 {
00264     RegNickList *rnl = NULL;
00265     char *command, *tmpp;
00266     unsigned char *tmpup;
00267     int done = 0, db_version = 1;
00268     int line_num = 0, do_enc = 0;
00269 
00270 #ifdef REQ_EMAIL
00271     readRegData();
00272 #endif
00273 
00274     db.ns = fopen(NS_DB, "r");
00275 
00276     if (db.ns == NULL) {
00277         logDump(corelog, "Unable to open " NS_DB ": %s", strerror(errno));
00278         return;
00279     }
00280 
00281     while (!done) {
00282         if (!(sfgets(dbLine, 1024, db.ns))) {
00283             if (!done) {
00284                 unexpected_eof(NS_DB);
00285             }
00286             done = 1;
00287             fclose(db.ns);
00288             return;
00289         }
00290 
00291         line_num++;
00292 
00293         if (parse_init(&state, dbLine) != 0) {
00295             abort();
00296         }
00297 
00298         command = parse_getarg(&state);
00299         if (strcmp(command, "version") == 0) {
00300             tmpp = parse_getarg(&state);
00301             assert(tmpp);
00302 
00303             if (tmpp)
00304                 db_version = atoi(tmpp);
00305         }
00306         else
00307         if (strcmp(command, "nick") == 0) {
00308             rnl = (RegNickList *) oalloc(sizeof(RegNickList));
00309             char *sNick, *sUser, *sHost, *sPass;
00310 
00311             sNick = parse_getarg(&state);
00312             sUser = parse_getarg(&state);
00313             sHost = parse_getarg(&state);
00314             sPass = parse_getarg(&state);
00315 
00316             if (strlen(sNick) >= NICKLEN) {
00317                 fprintf(stderr,
00318                         NS_DB ":%d: " " Nickname '%.80s' exceeds "
00319                         " NICKLEN.\n", line_num, sNick);
00320                 sshutdown(-1);
00321             } 
00322 
00323             strcpy(rnl->nick, sNick);
00324             strncpyzt(rnl->user, sUser, USERLEN);
00325             SetDynBuffer(&rnl->host, sHost);
00326 
00327             if (db_version < 2)
00328             {
00329                 if (strlen(sPass) > PASSLEN) {
00330                     fprintf(stderr,
00331                         NS_DB ":%d: " " Password for nick '%s' "
00332                         " exceeds PASSLEN.\n", line_num, sNick);
00333                     sshutdown(-1);
00334                 }
00335 
00336                 strcpy((char *)rnl->password, xorit(sPass));
00337             }
00338             else {
00339                 char encType = *sPass;
00340 
00341                 if (encType == '@') {
00342                     if (strlen(sPass+1) > PASSLEN) {
00343                         fprintf(stderr,
00344                             NS_DB ":%d: " " Password for nick '%s' "
00345                             " exceeds PASSLEN.\n", line_num, sNick);
00346                         sshutdown(-1);
00347                     }
00348 
00349                     strcpy((char *)rnl->password, xorit(sPass + 1));
00350                     do_enc = 0;
00351                 }
00352                 else if (encType == '$') {
00353                     int q, len;
00354 
00355                     tmpup = fromBase64(sPass + 1, &len);
00356                     assert(tmpup);
00357 
00358                     for(q = 0; q < 16; q++)
00359                         rnl->password[q] = tmpup[q];
00360                     do_enc = 1;
00361                     FREE(tmpup);
00362                 }
00363                 else
00364                     rnl->password[0] = '\0';
00365             }
00366 
00367             rnl->timestamp = (time_t) atol(parse_getarg(&state));
00368             rnl->timereg = (time_t) atol(parse_getarg(&state));
00369             rnl->flags = atoi(parse_getarg(&state));
00370 
00371             if (db_version >= 3) {
00372                 const char *idString = parse_getarg(&state);
00373                 int av, bv;
00374 
00375                 sscanf(idString, "%X*%X", &av, &bv);
00376                 rnl->regnum.SetDirect(top_regnick_idnum, av, bv);               
00377             }
00378             else {
00379                 rnl->regnum.SetNext(top_regnick_idnum);
00380             }
00381 
00382             if (do_enc)
00383                 rnl->flags |= NENCRYPT;
00384             else
00385                 rnl->flags &= ~NENCRYPT;
00386 
00387             rnl->opflags = 0;
00388             rnl->idtime = DEF_NDELAY;
00389             ADD_MEMO_BOX(rnl);
00390             addRegNick(rnl);
00391         } else if (strcmp(command, "is") == 0) {
00392             char *data = parse_getarg(&state);
00393             if (rnl && data)
00394                 rnl->is_readtime = atol(data);
00395         } else if (strcmp(command, "oper") == 0) {
00396             char *opflags_s;
00397             if (rnl && (rnl == getRegNickData(parse_getarg(&state)))) {
00398                 if ((opflags_s = parse_getarg(&state)))
00399                     rnl->opflags |=
00400                         (strtoul(opflags_s, (char **)0, 10) &
00401                          ~(OROOT | OSERVOP));
00402                 if (rnl->opflags)
00403                     addOpData(rnl);
00404             }
00405         } else if (strcmp(command, "url") == 0) {
00406             if (rnl && (rnl == getRegNickData(parse_getarg(&state))))
00407             {
00408                 rnl->url = strdup(parse_getarg(&state));
00409                 if (strlen(rnl->url) > (URLLEN - 1))
00410                     rnl->url[URLLEN - 1] = '\0';
00411             }
00412         } else if (strcmp(command, "gecos") == 0) {
00413 #ifdef TRACK_GECOS
00414             char *gecos = parse_getallargs(&state);
00415             if (gecos != NULL)
00416                 rnl->gecos = strdup(gecos);
00417 #endif
00418         } else if (strcmp(command, "akey") == 0) {
00419 #ifdef REQ_EMAIL
00420             if (rnl)
00421                 rnl->email_key = atoi(parse_getarg(&state));
00422 #endif
00423         } else if (strcmp(command, "chkey") == 0) {
00424             char *tmpp = parse_getarg(&state);
00425             if (rnl && tmpp)
00426                 rnl->chpw_key = strtoul(tmpp, (char **)0, 16);
00427         } else if (strcmp(command, "markby") == 0) {
00428             char *mby;
00429 
00430             rnl = getRegNickData(parse_getarg(&state));
00431             if (!rnl || !(mby = parse_getarg(&state)))
00432                 continue;
00433             rnl->markby = strdup(mby);
00434         } else if (strcmp(command, "access") == 0) {
00435             rnl = getRegNickData(parse_getarg(&state));
00436             addAccItem(rnl, parse_getarg(&state));
00437         } else if (!strcmp(command, "email")) {
00438             rnl = getRegNickData(parse_getarg(&state));
00439 
00440             strncpyzt(rnl->email, parse_getarg(&state), EMAILLEN);
00441 
00442             if (!strcmp(rnl->email, "(none)"))
00443                 strcat(rnl->email, " ");
00444         } else if (!strcmp(command, "idtime")) {
00445             rnl = getRegNickData(parse_getarg(&state));
00446             rnl->idtime = atoi(parse_getarg(&state));
00447         } else if (!strcmp(command, "done"))
00448             done = 1;
00449         else {
00450             fprintf(stderr, NS_DB ":%d: Error reading nick data (%s)",
00451                     line_num, dbLine);
00452             sshutdown(-1);
00453         }
00454 #ifdef DBDEBUG
00455         sSend(":%s PRIVMSG " DEBUGCHAN " :Read nick data (%s)", NICKSERV,
00456               dbLine);
00457 #endif
00458 
00459         parse_cleanup(&state);
00460     }
00461     fclose(db.ns);
00462 
00463     readMemoData();
00464 }
00465 
00466 /* ChanServ, all things ChanServ, and the like.... */
00467 
00471 void saveChanUrls(RegChanList * first)
00472 {
00473 return;
00474     RegChanList *cl = first;
00475     const char *tmpFounderNick;
00476     FILE *fpOut;
00477     char cstr[258];
00478 
00479     fpOut = fopen(CS_DIR "urls.txt.new", "w");
00480     if (!fpOut)
00481         return;
00482     while (cl) {
00483         if (!cl->name || !*cl->name || (cl->restrictlevel > 0)) {
00484             cl = cl->next;
00485             continue;
00486         }
00487         if (cl->mlock & (PM_S | PM_P | PM_I | PM_K)) {
00488             cl = cl->next;
00489             continue;
00490         }
00491 
00492         tmpFounderNick = cl->founderId.getNick();
00493 
00494         if (tmpFounderNick == NULL)
00495             tmpFounderNick = "-";
00496 
00497         sprintf(cstr, ":%.255s",
00498                 md5_printable(md5_password((u_char *)cl->password)));
00499         fprintf(fpOut, "C %s %s %ld %ld %s\n", cl->name,
00500                 tmpFounderNick, cl->mlock, cl->flags,
00501                 md5_printable(md5_password((u_char *)cstr)));
00502         memset(cstr, 0, 255);
00503         if (cl->topic)
00504             fprintf(fpOut, "topic %s %s %lu :%s\n", cl->name,
00505                     *cl->tsetby ? cl->tsetby : "-", cl->ttimestamp,
00506                     cl->topic);
00507         if (cl->url)
00508             fprintf(fpOut, "url %s :%s\n", cl->name, cl->url);
00509         fflush(fpOut);
00510         cl = cl->next;
00511     }
00512     if (fclose(fpOut) < 0) {
00513         sSend("GLOBOPS :Fatal error in writing out urls.txt.new.");
00514         return;
00515     }
00516     if (rename("chanserv/urls.txt.new", "chanserv/urls.txt") < 0) {
00517         sSend("GLOBOPS :Error renaming urls.txt.new.");
00518         return;
00519     }
00520 }
00521 
00525 void saveChanData(RegChanList * first)
00526 {
00527     RegChanList *cl = first;
00528     cAccessList *accitem;
00529     cAkickList *akitem;
00530     const char *tmpFounderNick;
00531     int x = 0;
00532 
00533     saveChanUrls(first);
00534     db.cs = NULL;
00535     db.cs = fopen(CS_DIR "chanserv.db", "w");
00536     if (db.cs == NULL) {
00537         sSend(":%s GOPER :ERROR: Unable to save Channel database fopen failed: %s", myname, strerror(errno));
00538         logDump(corelog, "Unable to save Channel database: fopen: %s", strerror(errno));
00539         return;
00540     }
00541 
00542     fprintf(db.cs, "version 2\n");
00543 
00544     while (cl) {
00545         tmpFounderNick = cl->founderId.getNick();
00546 
00547         if (tmpFounderNick == NULL)
00548             tmpFounderNick = "-";
00549 
00550         /* New chan insertion point */
00551         fprintf(db.cs, "channel ");
00552         fprintf(db.cs, "%s %s ", cl->name, tmpFounderNick);
00553         fprintf(db.cs, "%lu %lu ", cl->mlock, cl->flags);
00554 
00555         if (!(cl->flags & CENCRYPT)) {
00556             strcpy(cryptstr, (char *)cl->password);
00557             xorit(cryptstr);
00558             fprintf(db.cs, "@%s ", cryptstr);
00559         }
00560         else {
00561             u_char *tmpup = toBase64(cl->password, 16);
00562             if (!tmpup)
00563                 abort();
00564             fprintf(db.cs, "$%s ", tmpup);
00565             FREE(tmpup);
00566         }
00567 
00568         fprintf(db.cs, "%lu ", (unsigned long)cl->timereg);
00569         fprintf(db.cs, "%lu ", (unsigned long)cl->timestamp);
00570         fprintf(db.cs, "%s %lu %i ", cl->key, cl->limit, cl->memolevel);
00571         fprintf(db.cs, "%i %i :%s\n", cl->tlocklevel, cl->restrictlevel,
00572                 cl->desc);
00573         if (cl->topic)
00574             fprintf(db.cs, "topic %s %s %lu :%s\n", cl->name, cl->tsetby,
00575                     cl->ttimestamp, cl->topic);
00576         if (cl->url)
00577             fprintf(db.cs, "url %s :%s\n", cl->name, cl->url);
00578         if (cl->autogreet != NULL)
00579             fprintf(db.cs, "autogreet %s :%s\n", cl->name, cl->autogreet);
00580         if ((cl->flags & CMARK) && cl->markby)
00581             fprintf(db.cs, "markby %s %s\n", cl->name, cl->markby);
00582         if (cl->chpw_key)
00583             fprintf(db.cs, "chkey %X\n", cl->chpw_key);
00584         if (cl->firstOp) {
00585             const char *tmpName;
00586 
00587             for (accitem = cl->firstOp; accitem; accitem = accitem->next) {
00588                 tmpName = (accitem->nickId).getNick();
00589 
00590                 if (tmpName == NULL)
00591                     continue;
00592                 fprintf(db.cs, "op %s %s ", cl->name, tmpName);
00593                 fprintf(db.cs, "%i\n", accitem->uflags);
00594             }
00595         }
00596 
00597         if (cl->firstAkick) {
00598             for (akitem = cl->firstAkick; akitem; akitem = akitem->next) {
00599                 fprintf(db.cs, "akick %s %s ", cl->name, akitem->mask);
00600                 fprintf(db.cs, "%lu :%s\n", (u_long) akitem->added,
00601                         akitem->reason);
00602             }
00603         }
00604         x++;
00605         fflush(db.cs);
00606 #ifdef DBDEBUG
00607         sSend(":%s PRIVMSG " DEBUGCHAN " :Saved chan data (%s)", ChanServ,
00608               cl->name);
00609 #endif
00610         cl = cl->next;
00611     }
00612     fprintf(db.cs, "done\n");
00613     fflush(db.cs);
00614     fclose(db.cs);
00615 #ifdef GLOBOPS_TIMED_MSGS
00616     sSend(":%s GLOBOPS :Completed save(%i)", ChanServ, x);
00617 #else
00618     sSend(":%s PRIVMSG " LOGCHAN " :Completed save(%i)", ChanServ, x);
00619 #endif
00620 }
00621 
00625 void readChanData()
00626 {
00627     RegChanList *rcl;
00628     RegNickList *rnl;
00629     char *command;
00630     int done = 0, db_version = 1;
00631     int line_num = 0;
00632     char *pass;
00633     char *topic;
00634 
00635     rcl = NULL;
00636     rnl = NULL;
00637     db.cs = fopen(CS_DIR "chanserv.db", "r");
00638     if (db.cs == NULL)
00639         return;
00640 
00641     while (!done) {
00642         if ((sfgets(dbLine, 2048, db.cs)) == 0) {
00643             if (!done) {
00644                 unexpected_eof(CS_DB);
00645             }
00646             done = 1;
00647             fclose(db.cs);
00648             return;
00649         }
00650         line_num++;
00651 
00652         if (parse_init(&state, dbLine) != 0) {
00653             fprintf(stderr,
00654                     CS_DIR "chanserv.db:%d: " " Fatal error during read "
00655                     " (Null line?) \n", line_num);
00656             abort();
00657         }
00658 
00659         command = parse_getarg(&state);
00660         if (!strcmp(command, "version")) {
00661             db_version = atoi(parse_getarg(&state));
00662         }
00663         else if (!strcmp(command, "channel")) {
00664             char *sChannelName, *sFounderNick;
00665 
00666             rcl = (RegChanList *) oalloc(sizeof(RegChanList));
00667             sChannelName = parse_getarg(&state);
00668             sFounderNick = parse_getarg(&state);
00669             initRegChanData(rcl);
00670 
00671             if (strlen(sChannelName) >= CHANLEN) {
00672                 fprintf(stderr,
00673                         CS_DIR "chanserv.db:%d: "
00674                         " Channel name '%.80s' exceeds " " CHANLEN.\n",
00675                         line_num, sChannelName);
00676                 sshutdown(-1);
00677             } else if (strlen(sFounderNick) >= NICKLEN) {
00678                 fprintf(stderr,
00679                         CS_DIR "chanserv.db:%d: "
00680                         " Founder nick name '%.80s' " " (%.80s)"
00681                         " exceeds NICKLEN.\n", line_num, sFounderNick,
00682                         sChannelName);
00683                 sshutdown(-1);
00684             }
00685 
00686             if (!sChannelName || !sFounderNick) {
00687                 fprintf(stderr,
00688                         CS_DIR "chanserv.db:%d: " " Parse error. (%p, %p)",
00689                         line_num, sChannelName, sFounderNick);
00690                 sshutdown(-1);
00691             }
00692 
00693             rnl = getRegNickData(sFounderNick);
00694 
00695             strcpy(rcl->name, sChannelName);
00696 
00697             if (rnl)
00698             {
00699                 rcl->founderId = rnl->regnum;
00700                 rnl->chans++;
00701             }
00702             else
00703                 rcl->founderId = RegId(0, 0);
00704 
00705             rcl->mlock = atol(parse_getarg(&state));
00706             rcl->flags = atol(parse_getarg(&state));
00707             pass = parse_getarg(&state);    
00708             if (!pass) {
00709                 fprintf(stderr,
00710                         CS_DIR "chanserv.db:%d: " " Null password?!",
00711                         line_num);
00712                 sshutdown(-1);
00713             } 
00714 
00715             if ((db_version < 2) || *pass == '@') {
00716                 if (db_version < 2)
00717                     strcpy((char *)rcl->password, pass);
00718                 else
00719                     strcpy((char *)rcl->password, pass + 1);
00720 
00721                 if (strlen(pass) > (u_int)((db_version < 2) ? PASSLEN : PASSLEN+1)) {
00722                     fprintf(stderr,
00723                             CS_DIR "chanserv.db:%d: " " password > PASSLEN",
00724                             line_num);
00725                     sshutdown(-1);
00726                 }
00727 
00728                 xorit((char *)rcl->password);
00729                 rcl->flags &= ~CENCRYPT;
00730             }
00731             else {
00732                 u_char *tmpup = fromBase64(pass+1, NULL);
00733                 int q;
00734 
00735                 if (!tmpup)
00736                     abort();
00737                 for(q = 0; q < 16; q++)
00738                     rcl->password[q] = tmpup[q];
00739                 FREE(tmpup);
00740                 rcl->flags |= CENCRYPT;
00741             }
00742 
00743             rcl->timereg = (time_t) atol(parse_getarg(&state));
00744             rcl->timestamp = (time_t) atol(parse_getarg(&state));
00745             strncpyzt(rcl->key, parse_getarg(&state), KEYLEN);
00746             rcl->limit = atol(parse_getarg(&state));
00747             rcl->memolevel = atoi(parse_getarg(&state));
00748             rcl->tlocklevel = atoi(parse_getarg(&state));
00749             rcl->restrictlevel = atoi(parse_getarg(&state));
00750             /* The rest of the line - skipping the leading : */
00751             topic = parse_getallargs(&state);
00752             if (topic == NULL)
00753                 rcl->desc[0] = '\0';
00754             else
00755                 strncpyzt(rcl->desc, topic, CHANDESCBUF);
00756             addRegChan(rcl);
00757             mostchans++;
00758         } else if (!strcmp(command, "topic")) {
00759             rcl = getRegChanData(parse_getarg(&state));
00760             if (rcl == NULL)
00761                 continue;
00762             strncpyzt(rcl->tsetby, parse_getarg(&state), NICKLEN);
00763             rcl->ttimestamp = (time_t) atol(parse_getarg(&state));
00764             /* The rest of the topic, skipping the : in it */
00765             topic = parse_getallargs(&state);
00766             if (topic == NULL)
00767                 rcl->topic = NULL;
00768             else
00769                 rcl->topic = strdup(topic);
00770         } else if (!strcmp(command, "url")) {
00771             rcl = getRegChanData(parse_getarg(&state));
00772             if (rcl == NULL)
00773                 continue;
00774             topic = parse_getallargs(&state);
00775             if (topic == NULL)
00776                 rcl->url = NULL;
00777             else
00778                 rcl->url = strdup(topic);
00779         } else if (!strcmp(command, "autogreet")) {
00780             rcl = getRegChanData(parse_getarg(&state));
00781             if (rcl == NULL)
00782                 continue;
00783             topic = parse_getallargs(&state);
00784             if (topic == NULL)
00785                 rcl->autogreet = NULL;
00786             else
00787                 rcl->autogreet = strdup(topic);
00788         } else if (!strcmp(command, "markby")) {
00789             rcl = getRegChanData(parse_getarg(&state));
00790             if (rcl == NULL)
00791                 continue;
00792             topic = parse_getarg(&state);
00793             if (topic == NULL)
00794                 rcl->markby = NULL;
00795             else
00796                 rcl->markby = strdup(topic);
00797         } else if (strcmp(command, "chkey") == 0) {
00798             char *tmpp = parse_getarg(&state);
00799             if (rcl && tmpp)
00800                 rcl->chpw_key = strtoul(tmpp, (char **)0, 16);
00801         } else if (!strcmp(command, "op")) {
00802             cAccessList *lame;
00803             char *tmpName;
00804 
00805             rcl = getRegChanData(parse_getarg(&state));
00806             if (rcl == NULL)
00807                 continue;
00808             tmpName = parse_getarg(&state);
00809             if ((rnl = getRegNickData(tmpName)) != NULL)
00810             {
00811                 lame = (cAccessList *) oalloc(sizeof(cAccessList));
00812                 lame->nickId = rnl->regnum;
00813                 lame->uflags = atoi(parse_getarg(&state));
00814                 addChanOp(rcl, lame);
00815             }
00816         } else if (!strcmp(command, "akick")) {
00817             cAkickList *lame;
00818             lame = (cAkickList *) oalloc(sizeof(cAkickList));
00819             rcl = getRegChanData(parse_getarg(&state));
00820             if (rcl == NULL)
00821                 continue;
00822             strncpyzt(lame->mask, parse_getarg(&state), 70);
00823             lame->added = (time_t) atol(parse_getarg(&state));
00824             /* The rest of the string... */
00825             topic = parse_getallargs(&state);
00826             if (topic == NULL)
00827                 lame->reason[0] = '\0';
00828             else {
00829                 strncpyzt(lame->reason, topic, NICKLEN + 50);
00830             }
00831             addChanAkick(rcl, lame);
00832         } else if (!strcmp(command, "done"))
00833             done = 1;
00834         else {
00835             fprintf(stderr, "GLOBOPS :Read chan data (%s)", dbLine);
00836             sshutdown(-1);
00837         }
00838 
00839 #ifdef DBDEBUG
00840         sSend(":%s PRIVMSG " DEBGUGCHAN " :Read chan data (%s)", ChanServ,
00841               dbLine);
00842 #endif
00843 
00844         parse_cleanup(&state);
00845     }
00846     fclose(db.cs);
00847 }
00848 
00849 
00853 void saveMemoData(void)
00854 {
00855     RegNickList *nl;
00856     MemoBox *tmp;
00857     MemoList *memo;
00858     MemoBlock *mbitem;
00859     const char *tmpNickName;
00860     int bucket;
00861     int totmemos;
00862     int totboxes;
00863 
00864     db.ms = fopen(MS_DB, "w");
00865 
00866     totmemos = 0;
00867     totboxes = 0;
00868 
00869     for (bucket = 0; bucket < NICKHASHSIZE; bucket++) {
00870         nl = LIST_FIRST(&RegNickHash[bucket]);
00871 
00872         while (nl != NULL) {
00873             tmp = nl->memos;
00874             if (tmp != NULL) {
00875                 totboxes++;
00876                 fprintf(db.ms, "data %s %i %i %i %i\n", nl->nick,
00877                         tmp->memocnt, tmp->flags, tmp->flags, tmp->max);
00878                 if (tmp->forward)
00879                     fprintf(db.ms, "redirect %s %s\n", nl->nick,
00880                             tmp->forward->nick);
00881                 for (mbitem = tmp->firstMblock;
00882                      mbitem; 
00883                      mbitem = mbitem->next
00884                     )
00885                 {
00886                     if ((tmpNickName = mbitem->blockId.getNick()))
00887                         fprintf(db.ms, "mblock %s\n", tmpNickName);
00888                 }
00889 
00890                 for (memo = LIST_FIRST(&tmp->mb_memos); memo;
00891                      memo = LIST_NEXT(memo, ml_lst)) {
00892                     if (ShouldMemoExpire(memo, 0)) {
00893                         memo = LIST_NEXT(memo, ml_lst);
00894                         if (memo == NULL)
00895                             break;
00896                         continue;
00897                     }
00898                     fprintf(db.ms, "memo %s %i %i %lu %s %s :%s\n",
00899                             nl->nick, 0, memo->flags, (long)memo->sent,
00900                             memo->from, memo->to, memo->memotxt);
00901                     totmemos++;
00902                 }
00903             }
00904             nl = LIST_NEXT(nl, rn_lst);
00905         }
00906     }
00907 
00908     fprintf(db.ms, "done\n");
00909 
00910     fclose(db.ms);
00911 
00912     sSend(":%s PRIVMSG " LOGCHAN " :Completed save(%u boxes, %u memos)",
00913           MemoServ, totboxes, totmemos);
00914 }
00915 
00919 void readMemoData(void)
00920 {
00921     RegNickList *nick = NULL;
00922     RegNickList *from, *rnlb;
00923     MemoList *newmemo;
00924     char *command;
00925     char *topic;
00926     int done = 0;
00927     unsigned long linenum = 0;
00928 
00929     db.ms = fopen(MS_DB, "r");
00930 
00931     if (db.ms == NULL)
00932         return;
00933 
00934     while (!done) {
00935         if (!(sfgets(dbLine, 2048, db.ms))) {
00936             if (!done) {
00937                 unexpected_eof(MS_DB);
00938             }
00939             done = 1;
00940             fclose(db.ms);
00941             return;
00942         }
00943 
00944         linenum++;
00945         if (parse_init(&state, dbLine) != 0) {
00947             abort();
00948         }
00949 
00950         command = parse_getarg(&state);
00951 
00952         if (strcmp(command, "data") == 0) {
00953             nick = getRegNickData(parse_getarg(&state));
00955             (void)parse_getarg(&state);
00956             (void)parse_getarg(&state);
00957             if (nick) {
00958                 nick->memos->flags = atoi(parse_getarg(&state));
00959                 nick->memos->max = atoi(parse_getarg(&state));
00960                 if (nick->memos->max <= 0)
00961                     nick->memos->max = MS_DEF_RCV_MAX;
00962             }
00963         } else if (strcmp(command, "mblock") == 0) {
00964             MemoBlock *mbitem;
00965             if (nick && nick->memos) {
00966                 char *nickToBlock;
00967 
00968                 if ((nickToBlock = parse_getarg(&state))
00969                     && (rnlb = getRegNickData(nickToBlock))) {
00970 
00971                     mbitem = (MemoBlock *) oalloc(sizeof(MemoBlock));
00972                     mbitem->blockId = rnlb->regnum;
00973                     mbitem->next = nick->memos->firstMblock;
00974                     nick->memos->firstMblock = mbitem;
00975                 }
00976             }
00977         } else if (strcmp(command, "redirect") == 0) {
00978             nick = getRegNickData(parse_getarg(&state));
00979             if (nick != NULL)
00980                 nick->memos->forward =
00981                     getRegNickData(parse_getarg(&state));
00982         } else if (strcmp(command, "memo") == 0) {
00983             char *cn;
00984 
00985             cn = parse_getarg(&state);
00986 
00987             nick = getRegNickData(cn);
00988             if (nick == NULL) {
00989                 printf("memo: %s not valid\n", cn);
00990                 continue;
00991             }
00992             newmemo = (MemoList *) oalloc(sizeof(MemoList));
00993             newmemo->realto = nick;
00995             (void)parse_getarg(&state);
00996             newmemo->flags = atoi(parse_getarg(&state));
00997             newmemo->sent = (time_t) atol(parse_getarg(&state));
00998 
00999             /* Memo expiration code */
01000 
01001             if ((time(NULL) - newmemo->sent) >= NICKDROPTIME) {
01002                 FREE(newmemo);
01003                 continue;
01004             }
01005 
01006             strncpyzt(newmemo->from, parse_getarg(&state), NICKLEN);
01007             from = getRegNickData(newmemo->from);
01008             strncpyzt(newmemo->to, parse_getarg(&state), CHANLEN);
01009 
01010             /* Read the memo, skipping the leading : */
01011             topic = parse_getallargs(&state);
01012             if (topic == NULL) {
01013                 FREE(newmemo);
01014                 continue;
01015             }
01016             newmemo->memotxt = strdup(topic);
01017 
01018             /* Add the memo to the users memobox */
01019             LIST_ENTRY_INIT(newmemo, ml_lst);
01020             LIST_INSERT_HEAD(&nick->memos->mb_memos, newmemo, ml_lst);
01021             if (from && newmemo->flags & MEMO_UNREAD)
01022                 LIST_INSERT_HEAD(&from->memos->mb_sent, newmemo, ml_sent);
01023             nick->memos->memocnt++;
01024         } else if (strcmp(command, "done") == 0) {
01025             done = 1;
01026         } else {
01027             fprintf(stderr, "Error in reading memo data (%s) %lu\n",
01028                     dbLine, linenum);
01029             sshutdown(-1);
01030         }
01031 #ifdef DBDEBUG
01032         sSend(":%s PRIVMSG " DEBUGCHAN " :Read memo data (%s)", MemoServ,
01033               dbLine);
01034 #endif
01035 
01036         parse_cleanup(&state);
01037     }
01038 
01039     fclose(db.ms);
01040 }
01041 
01042 
01046 void saveTriggerData(void)
01047 {
01048     int totrules = 0;
01049     CloneRule *rule;
01050     extern CloneRule *first_crule;
01051 
01052     db.trigger = fopen(TRG_DB, "w");
01053     if (!db.trigger) {
01054         logDump(corelog,
01055                 "Unable to open trigger database for read access: %s",
01056                 strerror(errno));
01057         sSend
01058             (":%s GLOBOPS :Unable to open trigger database for read access: %s",
01059              NickServ, strerror(errno));
01060         return;
01061     }
01062     for (rule = first_crule; rule; rule = rule->next, ++totrules) {
01063         if (fprintf
01064             (db.trigger, "Trigger %s %d %d %ld\n", rule->mask,
01065              rule->trigger, rule->utrigger, rule->flags) < 0) {
01066             logDump(corelog,
01067                     "Error in writing rule to trigger database: %s",
01068                     strerror(errno));
01069             break;
01070         }
01071         if (rule->kill_msg) {
01072             if ((fprintf(db.trigger, "Killmsg %s\n", rule->kill_msg)) < 0) {
01073                 logDump(corelog,
01074                         "Error in writing trigger rule (killmsg) to trigger database: %s",
01075                         strerror(errno));
01076                 break;
01077             }
01078         }
01079         if (rule->warn_msg) {
01080             if ((fprintf(db.trigger, "Warnmsg %s\n", rule->warn_msg)) < 0) {
01081                 logDump(corelog,
01082                         "Error in writing trigger rule (warnlmsg) to trigger database: %s",
01083                         strerror(errno));
01084                 break;
01085             }
01086         }
01087     }
01088     fprintf(db.trigger, "done\n");
01089 
01090     if (fclose(db.trigger) < 0) {
01091         logDump(corelog, "Error closing trigger database: %s",
01092                 strerror(errno));
01093         return;
01094     }
01095     sSend(":%s PRIVMSG " LOGCHAN " :Completed trigger save (%u rules)",
01096           OperServ, totrules);
01097 }
01098 
01102 void readTriggerData(void)
01103 {
01104     CloneRule *rule = NULL;
01105     char *command, *text;
01106     int done = 0;
01107     unsigned long linenum = 0;
01108 
01109     db.trigger = fopen(TRG_DB, "r");
01110 
01111     if (db.trigger == NULL) {
01112         logDump(corelog,
01113                 "Unable to open trigger database for read access: %s",
01114                 strerror(errno));
01115         return;
01116     }
01117 
01118     while (!done) {
01119         if (!(sfgets(dbLine, 2048, db.trigger))) {
01120             if (!done) {
01121                 unexpected_eof(TRG_DB);
01122             }
01123             done = 1;
01124             fclose(db.trigger);
01125             return;
01126         }
01127         linenum++;
01128 
01129         if (parse_init(&state, dbLine) != 0) {
01131             abort();
01132         }
01133 
01134         command = parse_getarg(&state);
01135 
01136         if (strcmp(command, "Trigger") == 0) {
01137             rule = NewCrule();
01138             rule->kill_msg = NULL;
01139             rule->warn_msg = NULL;
01140             strncpyzt(rule->mask, parse_getarg(&state),
01141                       sizeof(rule->mask));
01142             rule->mask[sizeof(rule->mask) - 1] = '\0';
01143             rule->trigger = atoi(parse_getarg(&state));
01144             rule->utrigger = atoi(parse_getarg(&state));
01145             rule->flags = atol(parse_getarg(&state));
01146             AddCrule(rule, -2); /* -2 is magic for append to end */
01147         } else if (strcmp(command, "Killmsg") == 0) {
01148             text = parse_getallargs(&state);
01149             if (text && rule)
01150                 rule->kill_msg = strdup(text);
01151         } else if (strcmp(command, "Warnmsg") == 0) {
01152             text = parse_getallargs(&state);
01153             if (text && rule)
01154                 rule->warn_msg = strdup(text);
01155         } else if (strcmp(command, "done") == 0) {
01156             done = 1;
01157         } else {
01158             fprintf(stderr, "Error in reading trigger data (%s) %lu\n",
01159                     dbLine, linenum);
01160             parse_cleanup(&state);
01161             return;
01162         }
01163 #ifdef DBDEBUG
01164         sSend(":%s PRIVMSG " DEBUGCHAN " :Read trigger data (%s)",
01165               OperServ, dbLine);
01166 #endif
01167 
01168         parse_cleanup(&state);
01169     }
01170 
01171     fclose(db.trigger);
01172 }
01173 
01177 void saveInfoData(void)
01178 {
01179     SomeNews *news = NULL;
01180     int index = 0;
01181 
01182     db.is = fopen(IS_DB, "w");
01183 
01184     if (db.is == NULL) {
01185         sSend(":%s GOPER :ERROR: Unable to save Info Article database fopen failed: %s", myname, strerror(errno));
01186         logDump(corelog, "Unable to save Info Article database: fopen: %s", strerror(errno));
01187         return;
01188     }
01189 
01191     if (is_listhead != NULL) {  /* In case list is empty, thanks to Echostar for pointing this cack-handed error out...  */
01192         for (news = is_listhead, index = 0; news;
01193              news = news->next, index++)
01194         {
01195             fprintf(db.is, "article %i %s %li %s\n", news->importance,
01196                     news->from, news->timestamp, news->header);
01197             dbWriteString(db.is, news->content);
01198         }
01199     }
01200 
01201     fprintf(db.is, "done\n");
01202 
01203     fclose(db.is);
01204 
01205     sSend(":%s PRIVMSG " LOGCHAN " :Completed save(%i articles)", InfoServ,
01206           index);
01207 }
01208 
01209 
01213 void readInfoData(void)
01214 {
01215     char *command;
01216     int done = 0;
01217     SomeNews *news, *tmpnews;
01218 
01219     is_listhead = NULL;
01220     db.is = fopen(IS_DB, "r");
01221 
01222     if (db.is == NULL)
01223         return;
01224 
01234     while (!done) {
01235         if (!(sfgets(dbLine, 2048, db.is))) {
01236             if (!done) {
01237                 unexpected_eof(IS_DB);
01238             }
01239             done = 1;
01240             fclose(db.is);
01241             return;
01242         }
01243 
01244         if (parse_init(&state, dbLine) != 0) {
01246             abort();
01247         }
01248 
01249         command = parse_getarg(&state);
01250 
01251         if (!strcmp(command, "article")) {
01252             char *temp;
01253 
01254             news = (SomeNews *) oalloc(sizeof(SomeNews));
01255             if (!is_listhead)
01256                 is_listhead = news;
01257             else {
01258                 for (tmpnews = is_listhead; tmpnews->next;
01259                      tmpnews = tmpnews->next);
01260                 tmpnews->next = news;
01261             }
01262             news->importance = atoi(parse_getarg(&state));
01263             strncpyzt(news->from, parse_getarg(&state), NICKLEN);
01264             news->timestamp = atol(parse_getarg(&state));
01265             temp = parse_getallargs(&state);
01266             if (temp)
01267                 news->header = strdup(temp);
01268             news->content = dbReadString(db.is);
01269 
01270             if (news->timestamp > is_last_post_time)
01271                 is_last_post_time = news->timestamp;
01272         }
01273 
01274         else if (!strcmp(command, "done"))
01275             done = 1;
01276         else
01277             sshutdown(-1);
01278 
01279         parse_cleanup(&state);
01280     }
01281     fclose(db.is);
01282 }
01283 
01287 void sync_cfg(char *type)
01288 {
01289     void writeServicesTotals();
01290 
01291     switch (*type) {
01292     case '1':
01293         syncNickData((time_t) (CTime + (SYNCTIME * 3)));
01294         writeServicesTotals();
01295         nextCsync = (CTime + SYNCTIME);
01296         nextMsync = (CTime + (SYNCTIME * 2));
01297         nextNsync = (CTime + (SYNCTIME * 3));
01298         timer(SYNCTIME, sync_cfg, (void *)(const_cast<char *>("2")));
01299         break;
01300     case '2':
01301         syncChanData((time_t) (CTime + (SYNCTIME * 2)));
01302         writeServicesTotals();
01303         nextMsync = (CTime + SYNCTIME);
01304         nextNsync = (CTime + (SYNCTIME * 2));
01305         nextCsync = (CTime + (SYNCTIME * 2));
01306 
01307         timer(SYNCTIME, sync_cfg, (void *)const_cast<char *>("3"));
01308         break;
01309     case '3':
01310         syncMemoData((time_t) (CTime + SYNCTIME));
01311         nextNsync = nextCsync = nextMsync = (CTime + SYNCTIME);
01312         timer(SYNCTIME, sync_cfg, (void *)const_cast<char *>("4"));
01313         break;
01314     case '4':
01315         syncNickData((time_t) (CTime + SYNCTIME));
01316         syncChanData((time_t) (CTime + (SYNCTIME * 2)));
01317         syncMemoData((time_t) (CTime + (SYNCTIME * 3)));
01318         nextNsync = (CTime + SYNCTIME);
01319         nextCsync = (CTime + SYNCTIME * 2);
01320         nextMsync = (CTime + SYNCTIME * 3);
01321         timer(SYNCTIME, sync_cfg, (void *)const_cast<char *>("1"));
01322         break;
01323     }
01324 }
01325 
01326 #ifdef REQ_EMAIL
01327 void saveRegData(nRegList *first) {
01328         nRegList *nl = first;
01329         db.nsreg = fopen("nickserv/nickregs.db", "w");
01330         while(nl) {
01331                 fprintf(db.nsreg, "-\n%s\n%i\n", nl->email, nl->regs);
01332                 nl=nl->next;
01333                 fflush(db.nsreg);
01334         }
01335         fclose(db.nsreg);
01336 }
01337 
01338 void readRegData(void) {
01339         char tmp2[10 + HOSTLEN], tmp[10];
01340         int lineread = 1, i;
01341         db.nsreg = fopen("nickserv/nickregs.db", "r");
01342         if(!db.nsreg)
01343           return;
01344         while(sfgets(tmp, 10, db.nsreg)) {
01345                 if(tmp[0] != '-') {
01346                         sSend("GOPER :!ERROR! NickServ databases are corrupted (line %i)", lineread);
01347                         sshutdown(1);
01348                 }
01349                 sfgets(tmp2, 10 + HOSTLEN, db.nsreg);
01350                 addNReg(tmp2);
01351                 sfgets(tmp, 10, db.nsreg);
01352                 i = atoi(tmp);
01353                 for(i--;i;i--)
01354                   addNReg(tmp2);
01355         }
01356         fclose(db.nsreg);
01357 }
01358 #endif

Generated at Sat Oct 25 20:56:07 2003 for Services using Doxygen.
Services Copyr. 1996-2001 Chip Norkus, Max Byrd, Greg Poma, Michael Graff, James Hess, Dafydd James. All rights reserved See LICENSE for licensing information.