/* Conversion routines for Cygnus 0.2.0.
 *
 * IRC Services is copyright (c) 1996-2009 Andrew Church.
 *     E-mail: <achurch@achurch.org>
 * Parts written by Andrew Kempe and others.
 * This program is free but copyrighted software; see the file COPYING for
 * details.
 */

#include "convert-db.h"

#define WHAT(a,b) ((a)<<8 | (b))

/* Analog of strtok_remaining() from misc.c that strips newlines. */
static inline char *cyg_strtok_remaining() {
    char *s = strtok(NULL, "\r\n");
    while (s && *s == ' ')
	s++;
    return s;
}

/* Short macro to convert a string `str' to a time_t value with error
 * checking and store the result in the variable `var'; if the value is not
 * a valid integer, a message is printed to stderr and the current time is
 * used instead.  `errfmt' must be a string literal. */
#ifdef NO_VARARG_MACROS
#define CVT_TIME(var,str) do {						\
    unsigned long time_l = strtoul(str, (char **)&str, 10);		\
    if (str && *str) {							\
	fprintf(stderr, "%s:%d: time is not a valid number;"		\
		" setting to current time\n", fname, line);		\
	var = time(NULL);						\
    } else {								\
	var = time_l;							\
    }									\
} while (0)
#else  /* vararg macros */
#define CVT_TIME(var,str,errfmt,...) do {				\
    unsigned long time_l = strtoul(str, (char **)&str, 10);		\
    if (str && *str) {							\
	fprintf(stderr, "%s:%d: " errfmt " is not a valid number;"	\
		" setting to current time\n", fname, line , ##__VA_ARGS__);\
	var = time(NULL);						\
    } else {								\
	var = time_l;							\
    }									\
} while (0)
#endif

/* The same thing, but defaulting to zero instead of the current time
 * (good for expirations). */
#ifdef NO_VARARG_MACROS
#define CVT_TIME_0(var,str) do {					\
    unsigned long time_l = strtoul(str, (char **)&str, 10);		\
    if (str && *str) {							\
	fprintf(stderr, "%s:%d: time is not a valid number;"		\
		" setting to zero\n", fname, line);			\
	var = time(NULL);						\
    } else {								\
	var = time_l;							\
    }									\
} while (0)
#else  /* vararg macros */
#define CVT_TIME_0(var,str,errfmt,...) do {				\
    unsigned long time_l = strtoul(str, (char **)&str, 10);		\
    if (str && *str) {							\
	fprintf(stderr, "%s:%d: " errfmt " is not a valid number;"	\
		" setting to zero\n", fname, line , ##__VA_ARGS__);	\
	var = 0;							\
    } else {								\
	var = time_l;							\
    }									\
} while (0)
#endif

/*************************************************************************/

/* Reset memo limits to default? (-reset-memo-limits option) */
static int reset_memo_limits = 0;

/*************************************************************************/

/* Mapping from timezone IDs to offsets in minutes, based on cygnus.zone
 * distributed with Cygnus 0.2.0 */
static const int default_timezones[] = {
    -720,
    -660,
    -600,
    -600,
    -600,
    -540,
    -540,
    -540,
    -480,
    -480,
    -480,
    -420,
    -420,
    -360,
    -360,
    -300,
    -300,
    -240,
    -240,
    -240,
    -210,
    -210,
    -180,
    -180,
    -180,
    -150,
    -120,
    -60,
    0,
    0,
    0,
    0,
    0,
    60,
    60,
    60,
    60,
    60,
    60,
    60,
    60,
    120,
    120,
    120,
    120,
    120,
    120,
    120,
    180,
    180,
    180,
    180,
    210,
    240,
    300,
    330,
    360,
    390,
    420,
    480,
    480,
    480,
    480,
    540,
    540,
    570,
    600,
    600,
    630,
    660,
    720,
    720,
    720,
    780
};
static int *timezones, ntimezones;

static void cyg_load_nick(const char *sourcedir)
{
    FILE *f;
    char fname[PATH_MAX+1];
    char buf[4096];  /* Cygnus uses 2048; let's be safe */
    char *s;
    NickInfo *ni = NULL;
    NickGroupInfo *ngi = NULL;
    int ncount = 0, lcount = 0, mcount = 0;  /* to check against DE line */
    int line;

    snprintf(fname, sizeof(fname), "%s/nickserv.db", sourcedir);
    f = fopen(fname, "r");
    if (!f) {
	fprintf(stderr, "Cannot open %s: %s\n", fname, strerror(errno));
	exit(1);
    }
    fgets(buf, sizeof(buf), f);
    if (!(s = strtok(buf, " \r\n"))
     || strcmp(s,"NV") != 0
     || !(s = strtok(NULL, " \r\n"))
    ) {
	fprintf(stderr, "%s: Version number missing", fname);
	exit(1);
    }
    if (atoi(s) != 3) {
	fprintf(stderr, "%s: Bad version number (expected 3, got %d)\n",
		fname, atoi(s));
	exit(1);
    }

    line = 1;
    while (fgets(buf, sizeof(buf), f)) {
	line++;
	if (!(s = strtok(buf, " \r\n")))
	    continue;
	switch (WHAT(s[0],s[1])) {
	  case WHAT('S','S'):   /* Previous servicestamp value; ignore */
	    break;

	  case WHAT('N','I'): { /* Basic nickname info */
	    char *pass;  /* may be truncated */
	    const char *reg, *seen, *flags, *memolimit, *zone, *key,
	               *usermask, *realname;
	    long myflags, tmp;
	    int truncated = 0;

	    ncount++;
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt NI line, ignoring nickname\n",
			fname, line);
		ni = NULL;
		ngi = NULL;
		break;
	    }
	    if (strlen(s) > NICKMAX-1) {
		fprintf(stderr, "%s:%d: Truncating nickname `%s' (exceeds"
			" NICKMAX-1 characters)\n", fname, line, s);
		s[NICKMAX-1] = 0;
		truncated = 1;
	    }
	    if (get_nickinfo(s)) {
		fprintf(stderr, "%s:%d: Nickname `%s' already exists%s,"
			" skipping\n", fname, line, s,
			truncated ? " (due to truncation?)" : "");
		ni = NULL;
		ngi = NULL;
		break;
	    }
	    pass = strtok(NULL, " \r\n");
	    reg = strtok(NULL, " \r\n");
	    seen = strtok(NULL, " \r\n");
	    (void) strtok(NULL, " \r\n");  /* last ircd timestamp, not used */
	    (void) strtok(NULL, " \r\n");  /* last servicestamp, not used */
	    flags = strtok(NULL, " \r\n");
	    memolimit = strtok(NULL, " \r\n");
	    zone = strtok(NULL, " \r\n");
	    key = strtok(NULL, " \r\n");
	    usermask = strtok(NULL, " \r\n");
	    realname = strtok(NULL, " \r\n");
	    if (!pass || !reg || !seen || !flags || !memolimit || !zone
	     || !key || !usermask || !realname
	    ) {
		fprintf(stderr, "%s:%d: Corrupt NI line, ignoring nickname\n",
			fname, line);
		ni = NULL;
		ngi = NULL;
		break;
	    }
	    myflags = strtol(flags, (char **)&flags, 10);
	    if (flags && *flags) {
		fprintf(stderr, "%s:%d: Flags value for `%s' is not a"
			" valid number; ignoring nickname\n",
			fname, line, ni->nick);
		ni = NULL;
		ngi = NULL;
		break;
	    }
	    ni = makenick(s, &ngi);
	    if (!ni) {
		fprintf(stderr, "%s:%d: Unable to add nickname `%s' to"
			" database, skipping\n", fname, line, s);
		ni = NULL;
		ngi = NULL;
		break;
	    }
	    if (strlen(pass) > sizeof(ngi->pass)-1) {
		fprintf(stderr, "%s:%d: Password for `%s' truncated to %d"
			" characters\n", fname, line, ni->nick,
			sizeof(ngi->pass)-1);
		pass[sizeof(ngi->pass)-1] = 0;
	    }
	    strscpy(ngi->pass, pass, sizeof(ngi->pass));
#ifdef NO_VARARG_MACROS
	    CVT_TIME(ni->time_registered, reg);
	    CVT_TIME(ni->last_seen, seen);
#else
	    CVT_TIME(ni->time_registered, reg,
		     "Registration time for `%s'", ni->nick);
	    CVT_TIME(ni->last_seen, seen, "Last-seen time for `%s'", ni->nick);
#endif
	    tmp = strtol(memolimit, (char **)&memolimit, 10);
	    if (reset_memo_limits) {
		ngi->memos.memomax = MEMOMAX_DEFAULT;
	    } else if (memolimit && *memolimit) {
		fprintf(stderr, "%s:%d: Memo limit for `%s' is not a valid"
			" number; setting to default\n",
			fname, line, ni->nick);
		ngi->memos.memomax = MEMOMAX_DEFAULT;
	    } else if (tmp < 0 || tmp > MEMOMAX_MAX) {
		fprintf(stderr, "%s:%d: Memo limit for `%s' is out of range;"
			" setting to default\n", fname, line, ni->nick);
		ngi->memos.memomax = MEMOMAX_DEFAULT;
	    } else {
		ngi->memos.memomax = tmp;
	    }
	    tmp = strtol(zone, (char **)&zone, 10);
	    if (zone && *zone) {
		fprintf(stderr, "%s:%d: Timezone index for `%s' is not a"
			" valid number; setting to default\n",
			fname, line, ni->nick);
		ngi->timezone = TIMEZONE_DEFAULT;
	    } else if (tmp == 0 || ntimezones <= 0) {
		ngi->timezone = TIMEZONE_DEFAULT;
	    } else if (tmp < 1 || tmp > ntimezones) {
		fprintf(stderr, "%s:%d: Timezone index for `%s' is out of"
			" range; setting to default\n", fname, line, ni->nick);
		ngi->timezone = TIMEZONE_DEFAULT;
	    } else {
		ngi->timezone = timezones[tmp-1];
	    }
	    ngi->authcode = strtol(key, (char **)&key, 10);
	    if (key && *key) {
		fprintf(stderr, "%s:%d: Authentication code for `%s' is not a"
			" valid number; clearing\n", fname, line, ni->nick);
		ngi->authcode = 0;
	    } else if (ngi->authcode != 0) {
		ngi->authset = time(NULL);
		/* Assume it's a code for registration; if it's one for an
		 * E-mail change, we'll find that out later */
		ngi->authreason = NICKAUTH_REGISTER;
	    }
	    ni->last_usermask = sstrdup(usermask);
	    ni->last_realname = sstrdup(realname);
	    /* Now set flags */
	    ngi->flags = 0;
	    if (myflags & 0x0001)
		ngi->flags |= NF_KILLPROTECT;
	    if (myflags & 0x0002)
		ngi->flags |= NF_SECURE;
	    if (myflags & 0x0004)
		ngi->flags |= NF_HIDE_EMAIL;
	    if (myflags & 0x0080)
		ngi->flags |= NF_PRIVATE;
	    if (myflags & 0x0400)   /* NOMEMO */
		ngi->memos.memomax = 0;
	    if (myflags & 0x1000)   /* MEMOMAIL */
		ngi->flags |= NF_MEMO_FWDCOPY;  /* Cygnus uses forward+copy */
	    if (myflags & 0x2000) { /* FROZEN */
		ngi->suspendinfo = new_suspendinfo(
			"<unknown>",
			"Unknown (imported from Cygnus)",
			0
		);
	    }
	    if (myflags & 0x4000)   /* HELD, i.e. not expiring */
		ni->status |= NS_NOEXPIRE;
	    if (myflags & 0x20000000)
		ngi->os_priv = NP_SERVOPER;
	    if (myflags & 0x80000000) {
		if (!ngi->authcode) {
		    fprintf(stderr, "%s:%d: WAITAUTH set for `%s' but no"
			    " authentication code!  Corrupt database?",
			    fname, line, ni->nick);
		}
	    } else {
		if (ngi->authcode) {
		    fprintf(stderr, "%s:%d: `%s' has authentication code, but"
			    " WAITAUTH not set (corrupt database?); clearing"
			    " authcode", fname, line, ni->nick);
		    ngi->authcode = 0;
		    ngi->authset = 0;
		}
	    }
	    break;
	  } /* case WHAT('N','I') */

	  case WHAT('A','C'):   /* Access list entry */
	    if (!ngi)
		break;
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt AC line, ignoring\n",
			fname, line);
		break;
	    }
	    ARRAY_EXTEND(ngi->access);
	    ngi->access[ngi->access_count-1] = sstrdup(s);
	    break;

	  case WHAT('L','N'):   /* Subnick (link) list */
	    while ((s = strtok(NULL, " \r\n")) != NULL) {
		lcount++;
		if (ngi) {
		    int truncated = 0;
		    NickInfo *ni2;
		    if (strlen(s) > NICKMAX-1) {
			fprintf(stderr, "%s:%d: Truncating nickname `%s'"
				" (exceeds NICKMAX-1 characters)\n",
				fname, line, s);
			s[NICKMAX-1] = 0;
			truncated = 1;
		    }
		    if (get_nickinfo(s)) {
			fprintf(stderr, "%s:%d: Nickname `%s' (link to `%s')"
				" already exists%s, skipping\n",
				fname, line, s, ni->nick,
				truncated ? " (due to truncation?)" : "");
		    } else if (!(ni2 = makenick(s, NULL))) {
			fprintf(stderr, "%s:%d: Unable to add nickname `%s'"
				" (link to `%s') to database, skipping\n",
				fname, line, s, ni->nick);
		    } else {
			ni2->nickgroup = ni->nickgroup;
			ni2->time_registered = ni->time_registered;
			ni2->last_seen = ni->last_seen;
			ARRAY_EXTEND(ngi->nicks);
			strscpy(ngi->nicks[ngi->nicks_count-1], s,
				sizeof(*ngi->nicks));
		    }
		} else {
		    fprintf(stderr, "%s:%d: Ignoring link `%s' to ignored"
			    " nickname\n", fname, line, s);
		}
	    }
	    break;

	  case WHAT('M','O'): { /* Memo */
	    const char *num_s, *flags_s, *time_s, *text;
	    char *sender;  /* we modify it */
	    int i;
	    unsigned long num;
	    long flags, rtime;

	    mcount++;
	    if (!ngi)
		break;
	    num_s = strtok(NULL, " \r\n");
	    flags_s = strtok(NULL, " \r\n");
	    time_s = strtok(NULL, " \r\n");
	    sender = strtok(NULL, " \r\n");
	    text = cyg_strtok_remaining();
	    if (!num_s || !flags_s || !time_s || !sender || !text) {
		fprintf(stderr, "%s:%d: Corrupt MO line, ignoring\n",
			fname, line);
		break;
	    }
	    num = strtoul(num_s, (char **)&num_s, 10);
	    if (num_s && *num_s) {
		fprintf(stderr, "%s:%d: Memo number is not a valid number;"
			" ignoring memo\n", fname, line);
		break;
	    }
	    flags = strtol(flags_s, (char **)&flags_s, 10);
	    if (flags_s && *flags_s) {
		fprintf(stderr, "%s:%d: Flag value is not a valid number;"
			" ignoring memo\n", fname, line);
		break;
	    }
#ifdef NO_VARARG_MACROS
	    CVT_TIME(rtime, time_s);
#else
	    CVT_TIME(rtime, time_s, "Memo timestamp");
#endif
	    if (strlen(sender) > NICKMAX-1) {
		fprintf(stderr, "%s:%d: Truncating sender nickname `%s'"
			" (exceeds NICKMAX-1 characters)\n", fname, line,
			sender);
		sender[NICKMAX-1] = 0;
	    }
	    ARRAY_EXTEND(ngi->memos.memos);
	    i = ngi->memos.memos_count - 1;
	    ngi->memos.memos[i].number = num;
	    ngi->memos.memos[i].flags = 0;
	    if (flags & 1)
		ngi->memos.memos[i].flags |= MF_UNREAD;
	    ngi->memos.memos[i].time = rtime;
	    strscpy(ngi->memos.memos[i].sender, sender,
		    sizeof(ngi->memos.memos[i].sender));
	    ngi->memos.memos[i].text = sstrdup(text);
	    break;
	  } /* case WHAT('M','O') */

	  case WHAT('F','W'):   /* Nick to forward memos to; ignore */
	    break;

	  case WHAT('F','R'):   /* Freeze (suspend) reason */
	    if (!ngi)
		break;
	    s = cyg_strtok_remaining();
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt FR line, ignoring\n",
			fname, line);
		break;
	    }
	    if (!ngi->suspendinfo) {
		fprintf(stderr, "%s:%d: FR line for `%s', but FREEZE flag"
			" not set (corrupt database?), ignoring\n",
			fname, line, ni->nick);
		break;
	    }
	    /* Under normal circumstances si->reason will be a string
	     * constant at this point.  If there are ever two FR lines for
	     * the same nick group, we leak memory here, but since
	     * convert-db doesn't worry about memory leaks in general we
	     * don't worry about them here. */
	    ngi->suspendinfo->reason = sstrdup(s);
	    break;

	  case WHAT('E','M'):   /* E-mail address */
	    if (!ngi)
		break;
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt RM line, ignoring\n",
			fname, line);
		break;
	    }
	    if (ngi->email && ngi->authreason == NICKAUTH_SET_EMAIL) {
		/* New address already waiting for AUTH; ignore this one */
	    } else {
		free(ngi->email);
		ngi->email = sstrdup(s);
	    }
	    break;

	  case WHAT('T','E'):   /* Temporary E-mail address */
	    /* This is set when a SET EMAIL command has been given and the
	     * change has not yet been authenticated.  We discard the old
	     * address and store this one instead, then modify the auth
	     * code (or set one if needed) appropriately. */
	    if (!ngi)
		break;
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt TE line, ignoring\n",
			fname, line);
		break;
	    }
	    free(ngi->email);
	    ngi->email = sstrdup(s);
	    if (!ngi->authcode) {
		fprintf(stderr, "%s:%d: TE line for nickname `%s' without"
			" authentication code!  Assigning new code.\n",
			fname, line, ni->nick);
		ngi->authcode = rand()%900000000 + 100000000;
		ngi->authset = time(NULL);
	    }
	    ngi->authreason = NICKAUTH_SET_EMAIL;
	    break;

	  case WHAT('U','R'):   /* URL */
	    if (!ngi)
		break;
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt UR line, ignoring\n",
			fname, line);
		break;
	    }
	    ngi->url = sstrdup(s);
	    break;

	  case WHAT('U','N'):   /* UIN; ignore */
	    break;

	  case WHAT('N','A'):   /* "Real name" (not ni->last_realname); ignore */
	    break;

	  case WHAT('A','G'):   /* Age; ignore */
	    break;

	  case WHAT('S','X'):   /* Sex; ignore (TODO: get some) */
	    break;

	  case WHAT('L','O'):   /* Location; ignore */
	    break;

	  case WHAT('D','E'):   /* Database end; check counts */
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "Warning: check line corrupt; database may"
			" be invalid");
		break;
	    }
	    if (atoi(s) != ncount) {
		fprintf(stderr, "Warning: bad nickname count (%d expected,"
			" %d found)--database may be corrupt\n",
			atoi(s), ncount);
	    }
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "Warning: check line corrupt; database may"
			" be invalid");
		break;
	    }
	    if (atoi(s) != lcount) {
		fprintf(stderr, "Warning: bad link count (%d expected, %d"
			" found)--database may be corrupt\n", atoi(s), lcount);
	    }
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "Warning: check line corrupt; database may"
			" be invalid");
		break;
	    }
	    if (atoi(s) != mcount) {
		fprintf(stderr, "Warning: bad memo count (%d expected, %d"
			" found)--database may be corrupt\n", atoi(s), mcount);
	    }
	    break;
	} /* switch (WHAT(s)) */
    } /* while (fgets()) */

    fclose(f);
}

/*************************************************************************/

static struct {
    int32 flag;
    char mode;
} cmodes[] = {
    { 0x00000001, 't' },
    { 0x00000002, 'n' },
    { 0x00000004, 's' },
    { 0x00000008, 'm' },
    { 0x00000010, 'l' },
    { 0x00000020, 'i' },
    { 0x00000040, 'p' },
    { 0x00000080, 'k' },
    { 0x00000100, 0   }, /* cumode +o */
    { 0x00000200, 0   }, /* cumode +v */
    { 0x00000400, 'R' },
    { 0x00000800, 0   }, /* 'r', never set in mlock */
    { 0x00001000, 'c' },
    { 0x00002000, 'O' },
    { 0x00004000, 'Q' },
    { 0x00008000, 'S' },
    { 0x00010000, 'K' },
    { 0x00020000, 'V' },
    { 0x00040000, 'f' },
    { 0x00080000, 'H' },
    { 0x00100000, 'G' },
    { 0x00200000, 'C' },
    { 0x00400000, 'u' },
    { 0x00800000, 'z' },
    { 0x01000000, 'N' },
    { 0x02000000, 'L' },
    { 0x04000000, 'A' },
    { 0x08000000, 0   }, /* cumode +h */
    { 0x10000000, 0   }, /* cumode +a */
    { 0x20000000, 0   }, /* cumode +q */
    { 0, 0 }
};

static void cyg_load_chan(const char *sourcedir)
{
    FILE *f;
    char fname[PATH_MAX+1];
    char buf[4096];  /* Cygnus uses 2048; let's be safe */
    char *s;
    ChannelInfo *ci = NULL;
    int ccount = 0;  /* to check against DE line */
    int line, i;

    snprintf(fname, sizeof(fname), "%s/chanserv.db", sourcedir);
    f = fopen(fname, "r");
    if (!f) {
	fprintf(stderr, "Cannot open %s: %s\n", fname, strerror(errno));
	exit(1);
    }
    fgets(buf, sizeof(buf), f);
    if (!(s = strtok(buf, " \r\n"))
     || strcmp(s,"CV") != 0
     || !(s = strtok(NULL, " \r\n"))
    ) {
	fprintf(stderr, "%s: Version number missing", fname);
	exit(1);
    }
    if (atoi(s) != 3) {
	fprintf(stderr, "%s: Bad version number (expected 3, got %d)\n",
		fname, atoi(s));
	exit(1);
    }

    line = 1;
    while (fgets(buf, sizeof(buf), f)) {
	line++;
	if (!(s = strtok(buf, " \r\n")))
	    continue;
	switch (WHAT(s[0],s[1])) {
	  case WHAT('C','I'): { /* Basic channel info */
	    char *pass;  /* may be truncated */
	    const char *founder, *reg, *used, *flags, *lockon_s, *lockoff_s,
	               *topiclock;
	    char *on, *off;  /* for mode lock */
	    long myflags, lockon, lockoff, tmp;
	    int truncated = 0;
	    NickInfo *ni;

	    ccount++;
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt CI line, ignoring channel\n",
			fname, line);
		ci = NULL;
		break;
	    }
	    if (strlen(s) > CHANMAX-1) {
		fprintf(stderr, "%s:%d: Truncating channel name `%s' (exceeds"
			" CHANMAX-1 characters)\n", fname, line, s);
		s[CHANMAX-1] = 0;
		truncated = 1;
	    }
	    if (get_channelinfo(s)) {
		fprintf(stderr, "%s:%d: Channel `%s' already exists%s,"
			" skipping\n", fname, line, s,
			truncated ? " (due to truncation?)" : "");
		ci = NULL;
		break;
	    }
	    founder = strtok(NULL, " \r\n");
	    pass = strtok(NULL, " \r\n");
	    reg = strtok(NULL, " \r\n");
	    used = strtok(NULL, " \r\n");
	    flags = strtok(NULL, " \r\n");
	    lockon_s = strtok(NULL, " \r\n");
	    lockoff_s = strtok(NULL, " \r\n");
	    topiclock = strtok(NULL, " \r\n");
	    /* memolevel and key ignored */
	    if (!founder || !pass || !reg || !used || !flags || !lockon_s
	     || !lockoff_s || !topiclock
	    ) {
		fprintf(stderr, "%s:%d: Corrupt CI line, ignoring channel\n",
			fname, line);
		ci = NULL;
		break;
	    }
	    ni = get_nickinfo(founder);
	    if (!ni) {
		fprintf(stderr, "%s:%d: %s founder `%s' is not a registered"
			" nickname, ignoring channel\n",
			fname, line, s, founder);
		ci = NULL;
		break;
	    } else if (ni->status & NS_VERBOTEN) {
		fprintf(stderr, "%s:%d: %s founder `%s' is a forbidden"
			" nickname, ignoring channel\n",
			fname, line, s, founder);
		ci = NULL;
		break;
	    } else if (!ni->nickgroup) {
		fprintf(stderr, "%s:%d: %s fuonder `%s' has an invalid"
			" nickname record (BUG?), ignoring channel\n",
			fname, line, s, founder);
		ci = NULL;
		break;
	    }
	    myflags = strtol(flags, (char **)&flags, 10);
	    if (flags && *flags) {
		fprintf(stderr, "%s:%d: Flags value for `%s' is not a"
			" valid number; ignoring channel\n", fname, line, s);
		ci = NULL;
		break;
	    }
	    ci = makechan(s);
	    if (!ci) {
		fprintf(stderr, "%s:%d: Unable to add channel `%s' to"
			" database, skipping\n", fname, line, s);
		ci = NULL;
		break;
	    }
	    add_channelinfo(ci);
	    ci->founder = ni->nickgroup;
	    if (strlen(pass) > sizeof(ci->founderpass)-1) {
		fprintf(stderr, "%s:%d: Password for `%s' truncated to %d"
			" characters\n", fname, line, ci->name,
			sizeof(ci->founderpass)-1);
		pass[sizeof(ci->founderpass)-1] = 0;
	    }
	    strscpy(ci->founderpass, pass, sizeof(ci->founderpass));
#ifdef NO_VARARG_MACROS
	    CVT_TIME(ci->time_registered, reg);
	    CVT_TIME(ci->last_used, used);
#else
	    CVT_TIME(ci->time_registered, reg,
		     "Registration time for `%s'", ci->name);
	    CVT_TIME(ci->last_used, used, "Last-used time for `%s'", ci->name);
#endif
	    lockon = strtol(lockon_s, (char **)&lockon_s, 10);
	    if (lockon_s && *lockon_s) {
		fprintf(stderr, "%s:%d: Locked-on mode value for `%s' is not"
			" a valid number; clearing\n", fname, line, ci->name);
		lockon = 0;
	    }
	    lockoff = strtol(lockoff_s, (char **)&lockoff_s, 10);
	    if (lockoff_s && *lockoff_s) {
		fprintf(stderr, "%s:%d: Locked-off mode value for `%s' is not"
			" a valid number; clearing\n", fname, line, ci->name);
		lockoff = 0;
	    }
	    ci->mlock_on = on = scalloc(64, 1);
	    ci->mlock_off = off = scalloc(64, 1);
	    for (i = 0; cmodes[i].flag != 0; i++) {
		if (lockon & cmodes[i].flag)
		    *on++ = cmodes[i].mode;
		if (lockoff & cmodes[i].flag)
		    *off++ = cmodes[i].mode;
	    }
	    *on = 0;
	    *off = 0;
	    tmp = strtol(topiclock, (char **)&topiclock, 10);
	    if (topiclock && *topiclock) {
		fprintf(stderr, "%s:%d: Topic lock value for `%s' is not a"
			" valid number; clearing\n", fname, line, ci->name);
		tmp = 0;
	    } else if (tmp < 0 || tmp > 5) {
		fprintf(stderr, "%s:%d: Topic lock value for `%s' is out of"
			" range; clearing\n", fname, line, ci->name);
		tmp = 0;
	    }
	    ci->memos.memomax = MEMOMAX_DEFAULT;
	    /* Now set flags */
	    ci->flags = 0;
	    if (tmp)
		ci->flags |= CI_TOPICLOCK;
	    if (myflags & 0x0001)
		ci->flags |= CI_OPNOTICE;
	    if (myflags & 0x0004)
		ci->flags |= CI_SECURE;
	    if (myflags & 0x0008)
		ci->flags |= CI_RESTRICTED;
	    if (myflags & 0x0010) { /* FROZEN */
		ci->suspendinfo = new_suspendinfo(
			"<unknown>",
			"Unknown (imported from Cygnus)",
			0
		);
	    }
	    if (myflags & 0x0020)   /* HELD, i.e. not expiring */
		ci->flags |= CI_NOEXPIRE;
	    break;
	  } /* case WHAT('C','I') */

	  case WHAT('C','A'): {   /* Access list entry */
	    const char *nick = strtok(NULL, " \r\n");
	    const char *level_s = strtok(NULL, " \r\n");
	    const NickInfo *ni;
	    long level;
	    int i;
	    if (!ci)
		break;
	    if (!nick || !level_s) {
		fprintf(stderr, "%s:%d: Corrupt CA line, ignoring\n",
			fname, line);
		break;
	    }
	    ni = get_nickinfo(nick);
	    if (!ni) {
		fprintf(stderr, "%s:%d: %s access entry `%s' is not a"
			" registered nickname, ignoring entry\n",
			fname, line, ci->name, nick);
		break;
	    } else if (ni->status & NS_VERBOTEN) {
		fprintf(stderr, "%s:%d: %s access entry `%s' is a"
			" forbidden nickname, ignoring entry\n",
			fname, line, ci->name, nick);
		break;
	    } else if (!ni->nickgroup) {
		fprintf(stderr, "%s:%d: %s access entry `%s' has an"
			" invalid nickname record (BUG?), ignoring entry\n",
			fname, line, ci->name, nick);
		break;
	    } else if (ni->nickgroup == ci->founder) {
		fprintf(stderr, "%s:%d: %s access entry `%s' is the channel"
			" founder (or is in the same nickname group),"
			" ignoring entry\n", fname, line, ci->name, nick);
		break;
	    }
	    for (i = 0; i < ci->access_count; i++) {
		if (ni->nickgroup == ci->access[i].nickgroup) {
		    fprintf(stderr, "%s:%d: %s access entry `%s': nickname"
			    " group is already on the access list, ignoring"
			    " entry\n", fname, line, ci->name, nick);
		    break;
		}
	    }
	    if (i < ci->access_count)
		break;
	    level = strtol(level_s, (char **)&level_s, 10);
	    if (level_s && *level_s) {
		fprintf(stderr, "%s:%d: %s access entry `%s': Invalid"
			" access level, ignoring entry\n",
			fname, line, ci->name, nick);
		break;
	    } else if (level < 0 || level > 5) {
		fprintf(stderr, "%s:%d: %s access entry `%s': Access level"
			" out of range, ignoring entry\n",
			fname, line, ci->name, nick);
	    }
	    switch (level) {
		case 5:
		case 4: level = ACCLEV_SOP; break;
		case 3: level = ACCLEV_AOP; break;
		case 2: level = ACCLEV_HOP; break;
		case 1: level = ACCLEV_VOP; break;
		default: level = 0;         break;
 	    }
	    ARRAY_EXTEND(ci->access);
	    ci->access[ci->access_count-1].nickgroup = ni->nickgroup;
	    ci->access[ci->access_count-1].level = level;
	    break;
	  } /* case WHAT('C','A') */

	  case WHAT('A','K'): {   /* Autokick entry */
	    const char *mask = strtok(NULL, " \r\n");
	    const char *setter = strtok(NULL, " \r\n");
	    const char *time_s = strtok(NULL, " \r\n");
	    const char *reason = cyg_strtok_remaining();
	    time_t stime;
	    if (!ci)
		break;
	    if (!mask || !setter || !time_s) {
		fprintf(stderr, "%s:%d: Corrupt AK line, ignoring\n",
			fname, line);
		break;
	    }
	    if (reason && !*reason)
		reason = NULL;
#ifdef NO_VARARG_MACROS
	    CVT_TIME(stime, time_s);
#else
	    CVT_TIME(stime, time_s,
		     "%s autokick `%s': Set time", ci->name, mask);
#endif
	    ARRAY_EXTEND(ci->akick);
	    ci->akick[ci->akick_count-1].mask = sstrdup(mask);
	    ci->akick[ci->akick_count-1].reason = reason ? sstrdup(reason)
	                                                 : NULL;
	    strscpy(ci->akick[ci->akick_count-1].who, setter,
		    sizeof(ci->akick[ci->akick_count-1].who));
	    ci->akick[ci->akick_count-1].set = stime;
	    ci->akick[ci->akick_count-1].lastused = 0;
	    break;
	  } /* case WHAT('A','K') */

	  case WHAT('S','U'): { /* URL */
	    NickInfo *ni;
	    if (!ci)
		break;
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt UR line, ignoring\n",
			fname, line);
		break;
	    }
	    ni = get_nickinfo(s);
	    if (!ni) {
		fprintf(stderr, "%s:%d: %s successor `%s' is not a"
			" registered nickname, ignoring\n",
			fname, line, ci->name, s);
		break;
	    } else if (ni->status & NS_VERBOTEN) {
		fprintf(stderr, "%s:%d: %s successor `%s' is a forbidden"
			" nickname, ignoring\n", fname, line, ci->name, s);
		break;
	    } else if (!ni->nickgroup) {
		fprintf(stderr, "%s:%d: %s successor `%s' has an invalid"
			" nickname record (BUG?), ignoring\n",
			fname, line, ci->name, s);
		break;
	    } else if (ni->nickgroup == ci->founder) {
		fprintf(stderr, "%s:%d: %s successor `%s' is the channel"
			" founder (or is in the same nickname group),"
			" ignoring\n", fname, line, ci->name, s);
		break;
	    }
	    ci->successor = ni->nickgroup;
	    break;
	  } /* case WHAT('S','U') */

	  case WHAT('G','R'):   /* URL */
	    if (!ci)
		break;
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt GR line, ignoring\n",
			fname, line);
		break;
	    }
	    ci->entry_message = sstrdup(s);
	    break;

	  case WHAT('U','R'):   /* URL */
	    if (!ci)
		break;
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt UR line, ignoring\n",
			fname, line);
		break;
	    }
	    ci->url = sstrdup(s);
	    break;

	  case WHAT('C','T'): { /* Channel topic */
	    const char *nick = strtok(NULL, " \r\n");
	    const char *time_s = strtok(NULL, " \r\n");
	    const char *topic = cyg_strtok_remaining();
	    if (!nick || !time_s || !topic) {
		fprintf(stderr, "%s:%d: Corrupt CA line, ignoring\n",
			fname, line);
		break;
	    }
#ifdef NO_VARARG_MACROS
	    CVT_TIME(ci->last_topic_time, time_s);
#else
	    CVT_TIME(ci->last_topic_time, time_s,
		     "%s topic: Set time", ci->name);
#endif
	    strscpy(ci->last_topic_setter, nick,
		    sizeof(ci->last_topic_setter));
	    ci->last_topic = sstrdup(topic);
	    break;
	  } /* case WHAT('C','T') */

	  case WHAT('K','Y'):   /* Locked key */
	    if (!ci)
		break;
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt KY line, ignoring\n",
			fname, line);
		break;
	    }
	    if (!strchr(ci->mlock_on, 'k')) {
		fprintf(stderr, "%s:%d: Locked key given for channel %s"
			" without MLOCK +k, ignoring\n",
			fname, line, ci->name);
		break;
	    }
	    ci->mlock_key = sstrdup(s);
	    break;

	  case WHAT('L','M'): { /* Locked limit */
	    long limit;
	    if (!ci)
		break;
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt LM line, ignoring\n",
			fname, line);
		break;
	    }
	    if (!strchr(ci->mlock_on, 'l')) {
		fprintf(stderr, "%s:%d: Locked limit given for channel %s"
			" without MLOCK +l, ignoring\n",
			fname, line, ci->name);
		break;
	    }
	    limit = strtol(s, (char **)&s, 10);
	    if (s && *s) {
		fprintf(stderr, "%s:%d: Locked limit for channel %s is"
			" invalid, ignoring\n", fname, line, ci->name);
		s = strchr(ci->mlock_on, 'l');
		if (s)  /* always true, from check above */
		    memmove(s, s+1, strlen(s+1)+1);
		break;
	    }
	    ci->mlock_limit = limit;
	    break;
	  } /* case WHAT('L','M') */

	  case WHAT('F','L'):   /* Locked flood setting */
	    if (!ci)
		break;
	    s = cyg_strtok_remaining();
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt FL line, ignoring\n",
			fname, line);
		break;
	    }
	    if (!strchr(ci->mlock_on, 'f')) {
		fprintf(stderr, "%s:%d: Locked flood setting given for"
			" channel %s without MLOCK +f, ignoring\n",
			fname, line, ci->name);
		break;
	    }
	    ci->mlock_flood = sstrdup(s);
	    break;

	  case WHAT('L','K'):   /* Locked link */
	    if (!ci)
		break;
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt LK line, ignoring\n",
			fname, line);
		break;
	    }
	    if (!strchr(ci->mlock_on, 'L')) {
		fprintf(stderr, "%s:%d: Locked link given for channel %s"
			" without MLOCK +L, ignoring\n",
			fname, line, ci->name);
		break;
	    }
	    ci->mlock_link = sstrdup(s);
	    break;

	  case WHAT('V','F'):   /* Channel verify; ignore */
	    break;

	  case WHAT('D','E'):   /* Database end; check counts */
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "Warning: check line corrupt; database may"
			" be invalid");
		break;
	    }
	    if (atoi(s) != ccount) {
		fprintf(stderr, "Warning: bad channel count (%d expected,"
			" %d found)--database may be corrupt\n",
			atoi(s), ccount);
	    }
	    break;
	} /* switch (WHAT(s)) */
    } /* while (fgets()) */

    fclose(f);

    /* Sanity check for locked modes vs. their parameters */
    for (ci = first_channelinfo(); ci; ci = next_channelinfo()) {
	s = strchr(ci->mlock_on, 'k');
	if (s && !ci->mlock_key) {
	    fprintf(stderr, "%s: Channel %s has MLOCK +k but no locked key,"
		    " removing MLOCK +k", fname, ci->name);
	    memmove(s, s+1, strlen(s+1)+1);
	}
	s = strchr(ci->mlock_on, 'l');
	if (s && !ci->mlock_limit) {
	    fprintf(stderr, "%s: Channel %s has MLOCK +l but no locked limit,"
		    " removing MLOCK +l", fname, ci->name);
	    memmove(s, s+1, strlen(s+1)+1);
	}
	s = strchr(ci->mlock_on, 'f');
	if (s && !ci->mlock_flood) {
	    fprintf(stderr, "%s: Channel %s has MLOCK +k but no locked flood"
		    " setting, removing MLOCK +f", fname, ci->name);
	    memmove(s, s+1, strlen(s+1)+1);
	}
	s = strchr(ci->mlock_on, 'L');
	if (s && !ci->mlock_link) {
	    fprintf(stderr, "%s: Channel %s has MLOCK +L but no locked link,"
		    " removing MLOCK +L", fname, ci->name);
	    memmove(s, s+1, strlen(s+1)+1);
	}
    }
}

/*************************************************************************/

static void cyg_load_root(const char *sourcedir)
{
    FILE *f;
    char fname[PATH_MAX+1];
    char buf[4096];  /* Cygnus uses 2048; let's be safe */
    char *s;
    MaskData *md;
    int acount = 0, tcount = 0, ecount = 0;  /* to check against DE line */
    int line, i;

    snprintf(fname, sizeof(fname), "%s/rootserv.db", sourcedir);
    f = fopen(fname, "r");
    if (!f) {
	fprintf(stderr, "Cannot open %s: %s\n", fname, strerror(errno));
	exit(1);
    }
    fgets(buf, sizeof(buf), f);
    if (!(s = strtok(buf, " \r\n"))
     || strcmp(s,"RV") != 0
     || !(s = strtok(NULL, " \r\n"))
    ) {
	fprintf(stderr, "%s: Version number missing", fname);
	exit(1);
    }
    if (atoi(s) != 3) {
	fprintf(stderr, "%s: Bad version number (expected 3, got %d)\n",
		fname, atoi(s));
	exit(1);
    }

    line = 1;
    while (fgets(buf, sizeof(buf), f)) {
	line++;
	if (!(s = strtok(buf, " \r\n")))
	    continue;
	switch (WHAT(s[0],s[1])) {
	  case WHAT('A','K'): { /* Autokills / SGlines */
	    const char *mask = strtok(NULL, " \r\n");
	    const char *setter = strtok(NULL, " \r\n");
	    const char *realname = strtok(NULL, " \r\n");
	    const char *set = strtok(NULL, " \r\n");
	    const char *expires = strtok(NULL, " \r\n");
	    const char *reason = cyg_strtok_remaining();
	    acount++;
	    if (!mask || !setter || !realname || !set || !expires || !reason) {
		fprintf(stderr, "%s:%d: Corrupt AK line, ignoring\n",
			fname, line);
		break;
	    }
	    i = strtol(realname, (char **)&realname, 10);
	    if (realname && *realname) {
		fprintf(stderr, "%s:%d: Autokill `%s' realname value is"
			" invalid, ignoring autokill", fname, line, mask);
		break;
	    }
	    md = scalloc(sizeof(*md), 1);
	    md->mask = sstrdup(mask);
	    md->reason = sstrdup(reason);
	    strscpy(md->who, setter, sizeof(md->who));
#ifdef NO_VARARG_MACROS
	    CVT_TIME(md->time, set);
	    CVT_TIME_0(md->expires, expires);
#else
	    CVT_TIME(md->time, set, "Autokill `%s' set-time value", mask);
	    CVT_TIME_0(md->expires, expires,
		       "Autokill `%s' expires value", mask);
#endif
	    if (md->expires)
		md->expires += md->time;
	    md->lastused = 0;
	    add_maskdata(i ? MD_SGLINE : MD_AKILL, md);
	    break;
	  } /* case WHAT('A','K') */

	  case WHAT('T','R'): { /* Triggers (limited exceptions) */
	    const char *mask = strtok(NULL, " \r\n");
	    const char *limit_s = strtok(NULL, " \r\n");
	    int limit;
	    tcount++;
	    if (!mask || !limit_s) {
		fprintf(stderr, "%s:%d: Corrupt TR line, ignoring\n",
			fname, line);
		break;
	    }
	    limit = strtol(limit_s, (char **)&limit_s, 10);
	    if (limit_s && *limit_s) {
		fprintf(stderr, "%s:%d: Trigger `%s' limit value is invalid,"
			" ignoring trigger", fname, line, mask);
		break;
	    } else if (limit < 0) {
		fprintf(stderr, "%s:%d: Trigger `%s' limit value is negative,"
			" ignoring trigger", fname, line, mask);
		break;
	    } else if (limit == 0) {
		fprintf(stderr, "%s:%d: Trigger `%s' limit value is zero,"
			" converting to autokill", fname, line, mask);
	    } else if (limit > SESSION_MAXLIMIT) {
		limit = SESSION_MAXLIMIT;
		fprintf(stderr, "%s:%d: Trigger `%s' limit value is too large,"
			" setting to %d", fname, line, mask, limit);
	    }
	    md = scalloc(sizeof(*md), 1);
	    md->mask = sstrdup(mask);
	    md->limit = limit;
	    md->reason = sstrdup("Unknown (imported from Cygnus)");
	    strscpy(md->who, "<unknown>", sizeof(md->who));
	    md->time = time(NULL);
	    md->expires = 0;
	    md->lastused = 0;
	    add_maskdata(limit ? MD_EXCEPTION : MD_AKILL, md);
	    break;
	  } /* case WHAT('T','R') */

	  case WHAT('E','X'):   /* Exceptions (unlimited exceptions) */
	    ecount++;
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "%s:%d: Corrupt EX line, ignoring\n",
			fname, line);
		break;
	    }
	    md = scalloc(sizeof(*md), 1);
	    md->mask = sstrdup(s);
	    md->limit = 0;
	    md->reason = sstrdup("Unknown (imported from Cygnus)");
	    strscpy(md->who, "<unknown>", sizeof(md->who));
	    md->time = time(NULL);
	    md->expires = 0;
	    md->lastused = 0;
	    add_maskdata(MD_EXCEPTION, md);
	    break;

	  case WHAT('N','W'):   /* Last news update; ignore */
	    break;

	  case WHAT('R','S'):   /* Maximum uptime; ignore */
	    break;

	  case WHAT('D','E'):   /* Database end; check counts */
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "Warning: check line corrupt; database may"
			" be invalid");
		break;
	    }
	    if (atoi(s) != acount) {
		fprintf(stderr, "Warning: bad autokill count (%d expected, %d"
			" found)--database may be corrupt\n", atoi(s), acount);
	    }
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "Warning: check line corrupt; database may"
			" be invalid");
		break;
	    }
	    if (atoi(s) != tcount) {
		fprintf(stderr, "Warning: bad trigger count (%d expected, %d"
			" found)--database may be corrupt\n", atoi(s), tcount);
	    }
	    s = strtok(NULL, " \r\n");
	    if (!s) {
		fprintf(stderr, "Warning: check line corrupt; database may"
			" be invalid");
		break;
	    }
	    if (atoi(s) != ecount) {
		fprintf(stderr, "Warning: bad exception count (%d expected, %d"
			" found)--database may be corrupt\n", atoi(s), ecount);
	    }
	    break;
	} /* switch (WHAT(s)) */
    } /* while (fgets()) */

    fclose(f);
}

/*************************************************************************/
/*************************************************************************/

static const char *check_cygnus(const char *sourcedir)
{
    char buf[PATH_MAX+1];
    FILE *f;

    snprintf(buf, sizeof(buf), "%s/rootserv.db", sourcedir);
    f = fopen(buf, "r");
    if (f) {
	char *s;
	fgets(buf, sizeof(buf), f);
	fclose(f);
	s = strtok(buf, " \r\n");
	if (strcmp(s,"RV") == 0) {
	    s = strtok(NULL, " \r\n");
	    if (atoi(s) == 3)
		return "Cygnus 0.2.0";
	}
    }
    return NULL;
}

static void load_cygnus(const char *sourcedir, int verbose, int ac, char **av)
{
    int i;

    timezones = (int *)default_timezones;
    ntimezones = lenof(default_timezones);
    reset_memo_limits = 0;
    for (i = 1; i < ac; i++) {
	if (strncmp(av[i],"-tzfile=",8) == 0) {
	    const char *filename = av[i]+8;
	    FILE *f = fopen(filename, "r");
	    if (!f) {
		fprintf(stderr, "Cannot open time zone file `%s': %s\n",
			filename, strerror(errno));
	    } else {
		char buf[BUFSIZE];
		fprintf(stderr, "Using time zone file `%s'.\n", filename);
		ntimezones = 0;
		while (fgets(buf, sizeof(buf), f)) {
		    int n = atoi(buf);
		    if (n > ntimezones)
			ntimezones = n;
		}
		if (ntimezones <= 0) {
		    fprintf(stderr, "Warning: time zone file `%s' is empty\n",
			    filename);
		    timezones = NULL;
		} else {
		    int line = 0;
		    timezones = smalloc(sizeof(*timezones) * ntimezones);
		    memset(timezones, -1, sizeof(timezones));
		    fseek(f, 0, SEEK_SET);
		    while (fgets(buf, sizeof(buf), f)) {
			char *nstr, *ofsstr;
			int n, ofs;
			line++;
			nstr   = strtok(buf,  " \t\r\n");
			(void)   strtok(NULL, " \t\r\n");
			(void)   strtok(NULL, " \t\r\n");
			ofsstr = strtok(NULL, " \t\r\n");
			if (!nstr || !ofsstr) {
			    fprintf(stderr, "Warning: invalid format in time"
				    " zone file `%s' line %d\n",
				    filename, line);
			    continue;
			}
			n = strtol(nstr, &nstr, 10);
			ofs = strtol(ofsstr, &ofsstr, 10);
			if ((nstr && *nstr) || (ofsstr && *ofsstr)) {
			    fprintf(stderr, "Warning: invalid format in time"
				    " zone file `%s' line %d\n",
				    filename, line);
			} else if (n < 1 || n > ntimezones) {
			    fprintf(stderr, "Warning: invalid time zone index"
				    " in time zone file `%s' line %d\n",
				    filename, line);
			} else if (ofs < -12*60*60 || ofs > 13*60*60) {
			    fprintf(stderr, "Warning: time zone offset out of"
				    " range in time zone file `%s' line %d\n",
				    filename, line);
			} else {
			    timezones[n-1] = ofs/60;
			}
		    }
		}
	    }
	} else if (strcmp(av[i],"-no-timezones") == 0) {
	    ntimezones = 0;
	    if (timezones != default_timezones)
		free(timezones);
	    timezones = NULL;
	} else if (strcmp(av[i],"-reset-memo-limits") == 0) {
	    reset_memo_limits = 1;
	} else {
	    fprintf(stderr, "Unrecognized option %s\n", av[i]);
	    usage(av[0]);
	}
    }

    if (verbose)
	fprintf(stderr, "Loading nickserv.db...\n");
    cyg_load_nick(sourcedir);
    if (verbose)
	fprintf(stderr, "Loading chanserv.db...\n");
    cyg_load_chan(sourcedir);
    if (verbose)
	fprintf(stderr, "Loading rootserv.db...\n");
    cyg_load_root(sourcedir);
}

/*************************************************************************/
/*************************************************************************/

DBTypeInfo dbtype_cygnus = {
    "cygnus",
    check_cygnus,
    load_cygnus
};

/*************************************************************************/
