/* Conversion routines for Epona databases (version 1.3.0 and later).
 *
 * IRC Services is copyright (c) 1996-2002 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"

#if NICKMAX < 32
# error NICKMAX too small (must be >=32)
#elif CHANMAX < 64
# error CHANMAX too small (must be >=64)
#elif PASSMAX < 32
# error PASSMAX too small (must be >=32)
#endif

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

static void epona_load_nick(const char *sourcedir)
{
    dbFILE *f;
    int32 ver;
    int i, j, c;
    int16 tmp16;
    int32 tmp32;
    NickInfo *ni;
    NickGroupInfo *ngi;
    char *s;

    f = open_db_ver(sourcedir, "nick.db", 13, 13, &ver);

    /* Nick cores (nick core = nickname + NickGroupInfo) */
    for (i = 0; i < 1024; i++) {
	while ((c = getc_db(f)) == 1) {
	    char nickbuf[32];
	    ni = scalloc(sizeof(*ni), 1);
	    ngi = new_nickgroupinfo();
	    ni->nickgroup = ngi->id;
	    SAFE(read_string(&s, f));
	    strscpy(ni->nick, s, NICKMAX);
	    ngi->nicks = smalloc(sizeof(*ngi->nicks));
	    ngi->nicks_count = 1;
	    strscpy(ngi->nicks[0], ni->nick, NICKMAX);
	    SAFE(read_string(&s, f));
	    /* For some reason nick cores can get null passwords (???) */
	    if (!s) {
		fprintf(stderr, "Warning: nick `%s' has null password."
				"  Setting password to nickname.\n", ni->nick);
		s = ni->nick;
	    }
	    strscpy(ngi->pass, s, PASSMAX);
	    SAFE(read_string(&ngi->email, f));
	    SAFE(read_string(&s, f));  /* greet */
	    SAFE(read_int32(&tmp32, f));  /* icq */
	    SAFE(read_string(&ngi->url, f));
	    SAFE(read_int32(&tmp32, f));  /* flags */
	    if (tmp32 & 0x00000001)
		ngi->flags |= NF_KILLPROTECT;
	    if (tmp32 & 0x00000002)
		ngi->flags |= NF_SECURE;
	    if (tmp32 & 0x00000008)
		ngi->flags |= NF_MEMO_HARDMAX;
	    if (tmp32 & 0x00000010)
		ngi->flags |= NF_MEMO_SIGNON;
	    if (tmp32 & 0x00000020)
		ngi->flags |= NF_MEMO_RECEIVE;
	    if (tmp32 & 0x00000040)
		ngi->flags |= NF_PRIVATE;
	    if (tmp32 & 0x00000080)
		ngi->flags |= NF_HIDE_EMAIL;
	    if (tmp32 & 0x00000100)
		ngi->flags |= NF_HIDE_MASK;
	    if (tmp32 & 0x00000200)
		ngi->flags |= NF_HIDE_QUIT;
	    if (tmp32 & 0x00000400)
		ngi->flags |= NF_KILL_QUICK;
	    if (tmp32 & 0x00000800)
		ngi->flags |= NF_KILL_IMMED;
	    if (tmp32 & 0x00002000)		/* NI_SERVICES_ADMIN */
		ngi->os_priv = NP_SERVADMIN;
	    else if (tmp32 & 0x00001000)	/* NI_SERVICES_OPER */
		ngi->os_priv = NP_SERVOPER;
	    SAFE(read_int16(&ngi->language, f));
	    SAFE(read_int16(&ngi->access_count, f));
	    if (ngi->access_count) {
		char **access;
		access = scalloc(sizeof(char *), ngi->access_count);
		ngi->access = access;
		for (j = 0; j < ngi->access_count; j++, access++)
		    SAFE(read_string(access, f));
	    }
	    SAFE(read_int16(&ngi->memos.memos_count, f));
	    SAFE(read_int16(&ngi->memos.memomax, f));
	    if (ngi->memos.memos_count) {
		Memo *memos;
		memos = scalloc(sizeof(Memo), ngi->memos.memos_count);
		ngi->memos.memos = memos;
		for (j = 0; j < ngi->memos.memos_count; j++, memos++) {
		    SAFE(read_int32(&memos->number, f));
		    SAFE(read_int16(&tmp16, f));
		    if (tmp16 & 1)
			memos->flags |= MF_UNREAD;
		    SAFE(read_int32(&tmp32, f));
		    memos->time = tmp32;
		    SAFE(read_buffer(nickbuf, f));
		    strscpy(memos->sender, nickbuf, NICKMAX);
		    SAFE(read_string(&memos->text, f));
		}
	    }
	    SAFE(read_int16(&tmp16, f));  /* channelcount */
	    SAFE(read_int16(&tmp16, f));  /* channelmax */
	    add_nickinfo(ni);
	    add_nickgroupinfo(ngi);
	} /* while (getc_db(f) == 1) */
	if (c != 0) {
	    fprintf(stderr, "%s is corrupt, aborting.\n", f->filename);
	    exit(1);
	}
    } /* for (i) */

    /* Nick aliases */
    for (i = 0; i < 1024; i++) {
	while ((c = getc_db(f)) == 1) {
	    int islink = 0;
	    SAFE(read_string(&s, f));  /* nick */
	    ni = get_nickinfo(s);
	    if (!ni) {
		islink = 1;
		ni = scalloc(sizeof(*ni), 1);
		strscpy(ni->nick, s, NICKMAX);
	    }
	    SAFE(read_string(&ni->last_usermask, f));
	    if (!ni->last_usermask)
		ni->last_usermask = "@";
	    SAFE(read_string(&ni->last_realname, f));
	    if (!ni->last_realname)
		ni->last_realname = "";
	    SAFE(read_string(&ni->last_quit, f));
	    SAFE(read_int32(&tmp32, f));
	    ni->time_registered = tmp32;
	    SAFE(read_int32(&tmp32, f));
	    ni->last_seen = tmp32;
	    SAFE(read_int16(&tmp16, f));  /* status */
	    if (tmp16 & 0x0002) {
		ni->status |= NS_VERBOTEN;
		ni->nickgroup = 0;
	    }
	    if (tmp16 & 0x0004)
		ni->status |= NS_NOEXPIRE;
	    SAFE(read_string(&s, f));
	    if (islink) {
		NickInfo *root = get_nickinfo(s);
		if (!root) {
		    fprintf(stderr,
			    "Warning: nick alias %s has no core, discarding\n",
			    ni->nick);
		    continue;
		}
		ni->nickgroup = root->nickgroup;
		add_nickinfo(ni);
		ngi = get_nickgroupinfo(ni->nickgroup);
		if (ngi) {
		    ARRAY_EXTEND(ngi->nicks);
		    strscpy(ngi->nicks[ngi->nicks_count-1], ni->nick, NICKMAX);
		} else {
		    fprintf(stderr, "Warning: Nick group %d for nick %s not"
			    " found -- program bug?  Output may be corrupt.",
			    ni->nickgroup, ni->nick);
		}
	    } else {
		if (stricmp(s, ni->nick) != 0) {
		    fprintf(stderr, "Warning: display %s for nick alias %s"
			    " different from nick core\n", s, ni->nick);
		}
	    }
	}
	if (c != 0) {
	    fprintf(stderr, "%s is corrupt, aborting.\n", f->filename);
	    exit(1);
	}
    }

    close_db(f);
}

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

static struct {
    int32 flag;
    char mode;
} epona_cmodes[] = {
    { 0x00000001, 'i' },
    { 0x00000002, 'm' },
    { 0x00000004, 'n' },
    { 0x00000008, 'p' },
    { 0x00000010, 's' },
    { 0x00000020, 't' },
    { 0x00000040, 'k' },
    { 0x00000080, 'l' },
    { 0x00000100, 'R' },
    { 0x00000200, 0   }, /* 'r', never set in mlock */
    { 0x00000400, 'c' },
    { 0x00008000, 'O' },
    { 0x00000800, 'A' },
    { 0x00001000, 'H' },
    { 0x00002000, 'K' },
    { 0x00004000, 'L' },
    { 0x00010000, 'Q' },
    { 0x00020000, 'S' },
    { 0x00040000, 'V' },
    { 0x00080000, 'f' },
    { 0x00100000, 'G' },
    { 0x00200000, 'C' },
    { 0x00400000, 'u' },
    { 0, 0 }
};

static void epona_load_chan(const char *sourcedir)
{
    dbFILE *f;
    int32 ver;
    int i, j, c;
    ChannelInfo *ci;
    NickInfo *ni;
    int32 tmp32, mlock_on, mlock_off;
    char *on, *off;

    f = open_db_ver(sourcedir, "chan.db", 14, 16, &ver);

    for (i = 0; i < 256; i++) {
	int16 tmp16;
	char *s;

	while ((c = getc_db(f)) == 1) {
	    char namebuf[64], passbuf[32], nickbuf[32];
	    ci = scalloc(sizeof(ChannelInfo), 1);
	    SAFE(read_buffer(namebuf, f));
	    strscpy(ci->name, namebuf, CHANMAX);
	    SAFE(read_string(&s, f));
	    if (s) {
		ni = get_nickinfo(s);
		if (!ni) {
		    fprintf(stderr,
			    "Warning: Founder %s for channel %s not found\n",
			    s, ci->name);
		} else if (!ni->nickgroup) {
		    fprintf(stderr, "Warning: Founder %s for channel %s is a"
			    " forbidden nick\n", s, ci->name);
		} else {
		    ci->founder = ni->nickgroup;
		}
	    }
	    SAFE(read_string(&s, f));
	    if (s) {
		ni = get_nickinfo(s);
		if (!ni) {
		    fprintf(stderr, "Warning: Successor %s for channel %s"
			    " not found\n", s, ci->name);
		} else if (!ni->nickgroup) {
		    fprintf(stderr, "Warning: Successor %s for channel %s"
			    " is a forbidden nick\n", s, ci->name);
		} else if (ni->nickgroup == ci->founder) {
		    fprintf(stderr, "Warning: Successor %s for channel %s"
			    " is the same as the founder, clearing\n",
			    s, ci->name);
		} else {
		    ci->successor = ni->nickgroup;
		}
	    }
	    SAFE(read_buffer(passbuf, f));
	    memcpy(ci->founderpass, passbuf, sizeof(passbuf));
	    SAFE(read_string(&ci->desc, f));
	    if (!ci->desc)
		ci->desc = "";
	    SAFE(read_string(&ci->url, f));
	    SAFE(read_string(&ci->email, f));
	    SAFE(read_int32(&tmp32, f));
	    ci->time_registered = tmp32;
	    SAFE(read_int32(&tmp32, f));
	    ci->last_used = tmp32;
	    SAFE(read_string(&ci->last_topic, f));
	    SAFE(read_buffer(nickbuf, f));
	    strscpy(ci->last_topic_setter, nickbuf, NICKMAX);
	    SAFE(read_int32(&tmp32, f));
	    ci->last_topic_time = tmp32;
	    SAFE(read_int32(&tmp32, f));  /* flags */
	    if (tmp32 & 0x00000001)
		ci->flags |= CI_KEEPTOPIC;
	    if (tmp32 & 0x00000002)
		ci->flags |= CI_SECUREOPS;
	    if (tmp32 & 0x00000004)
		ci->flags |= CI_PRIVATE;
	    if (tmp32 & 0x00000008)
		ci->flags |= CI_TOPICLOCK;
	    if (tmp32 & 0x00000010)
		ci->flags |= CI_RESTRICTED;
	    if (tmp32 & 0x00000040)
		ci->flags |= CI_SECURE;
	    if (tmp32 & 0x00000080)
		ci->flags |= CI_VERBOTEN;
	    if (tmp32 & 0x00000200)
		ci->flags |= CI_NOEXPIRE;
	    if (tmp32 & 0x00000400)
		ci->flags |= CI_MEMO_HARDMAX;
	    if (tmp32 & 0x00000800)
		ci->flags |= CI_OPNOTICE;
	    SAFE(read_string(&s, f));		/* forbidby */
	    SAFE(read_string(&s, f));		/* forbidreason */
	    SAFE(read_int16(&tmp16, f));	/* bantype */

	    SAFE(read_int16(&tmp16, f));
	    for (j = tmp16; j > 0; j--)
		SAFE(read_int16(&tmp16, f));

	    SAFE(read_int16(&ci->access_count, f));
	    if (ci->access_count) {
		ci->access = scalloc(ci->access_count, sizeof(ChanAccess));
		for (j = 0; j < ci->access_count; j++) {
		    SAFE(read_int16(&tmp16, f));  /* in_use */
		    if (tmp16) {
			SAFE(read_int16(&ci->access[j].level, f));
			SAFE(read_string(&s, f));
			SAFE(read_int32(&tmp32, f));	/* last used */
			if (s) {
			    ni = get_nickinfo(s);
			    if (ni)
				ci->access[j].nickgroup = ni->nickgroup;
			}
		    }
		}
	    }

	    SAFE(read_int16(&ci->akick_count, f));
	    if (ci->akick_count) {
		ci->akick = scalloc(ci->akick_count, sizeof(AutoKick));
		for (j = 0; j < ci->akick_count; j++) {
		    int16 is_nick = 0;
		    SAFE(read_int16(&tmp16, f));  /* in_use */
		    if (ver > 14) {
			is_nick = tmp16 & 2;
			tmp16 &= 1;
		    }
		    if (tmp16) {
			if (ver == 14)
			    SAFE(read_int16(&is_nick, f));
			SAFE(read_string(&s, f));
			if (is_nick && s) {
			    ci->akick[j].mask = smalloc(strlen(s)+5);
			    sprintf(ci->akick[j].mask, "%s!*@*", s);
			} else {
			    ci->akick[j].mask = s;
			}
			SAFE(read_string(&s, f));
			if (ci->akick[j].mask)
			    ci->akick[j].reason = s;
			SAFE(read_string(&s, f));
			if (ci->akick[j].mask)
			    strscpy(ci->akick[j].who, s, NICKMAX);
			SAFE(read_int32(&tmp32, f));
			if (ci->akick[j].mask)
			    ci->akick[j].set = tmp32;
		    }
		}
	    }

	    SAFE(read_int32(&mlock_on, f));
	    SAFE(read_int32(&mlock_off, f));
	    ci->mlock_on = on = scalloc(64, 1);
	    ci->mlock_off = off = scalloc(64, 1);
	    for (j = 0; epona_cmodes[j].flag != 0; j++) {
		if (mlock_on & epona_cmodes[j].flag)
		    *on++ = epona_cmodes[j].mode;
		if (mlock_off & epona_cmodes[j].flag)
		    *off++ = epona_cmodes[j].mode;
	    }
	    SAFE(read_int32(&ci->mlock_limit, f));
	    SAFE(read_string(&ci->mlock_key, f));
	    SAFE(read_string(&ci->mlock_flood, f));
	    SAFE(read_string(&ci->mlock_link, f));

	    SAFE(read_int16(&ci->memos.memos_count, f));
	    SAFE(read_int16(&tmp16, f));  /* memomax */
	    ci->memos.memomax = MEMOMAX_DEFAULT;
	    if (ci->memos.memos_count) {
		Memo *memos;
		memos = scalloc(sizeof(Memo), ci->memos.memos_count);
		ci->memos.memos = memos;
		for (j = 0; j < ci->memos.memos_count; j++, memos++) {
		    SAFE(read_int32(&memos->number, f));
		    SAFE(read_int16(&tmp16, f));
		    if (tmp16 & 1)
			memos->flags |= MF_UNREAD;
		    SAFE(read_int32(&tmp32, f));
		    memos->time = tmp32;
		    SAFE(read_buffer(nickbuf, f));
		    strscpy(memos->sender, nickbuf, NICKMAX);
		    SAFE(read_string(&memos->text, f));
		}
	    }

	    SAFE(read_string(&ci->entry_message, f));

	    /* BotServ-related */
	    SAFE(read_string(&s, f));
	    SAFE(read_int32(&tmp32, f));
	    SAFE(read_int16(&tmp16, f));
	    for (j = tmp16; j > 0; j--)
		SAFE(read_int16(&tmp16, f));
	    SAFE(read_int16(&tmp16, f));
	    SAFE(read_int16(&tmp16, f));
	    SAFE(read_int16(&tmp16, f));
	    SAFE(read_int16(&tmp16, f));
	    SAFE(read_int16(&tmp16, f));
	    SAFE(read_int16(&tmp16, f));
	    for (j = tmp16; j > 0; j--) {
		SAFE(read_int16(&tmp16, f));
		if (tmp16) {
		    SAFE(read_string(&s, f));
		    SAFE(read_int16(&tmp16, f));
		}
	    }

	    add_channelinfo(ci);

	} /* while (getc_db(f) == 1) */
	if (c != 0) {
	    fprintf(stderr, "%s is corrupt, aborting.\n", f->filename);
	    exit(1);
	}
    } /* for (i) */
    close_db(f);

    /* Check for non-forbidden channels with no founder */
    for (ci = first_channelinfo(); ci; ci = next_channelinfo()) {
	if (!(ci->flags & CI_VERBOTEN) && !ci->founder)
	    del_channelinfo(ci);
    }
}

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

static void epona_load_oper(const char *sourcedir)
{
    dbFILE *f;
    int32 ver;
    int16 i, n, tmp16;
    int32 tmp32;
    char *s;
    MaskData *md;

    f = open_db_ver(sourcedir, "oper.db", 11, 13, &ver);
    if (ver == 12) {
	fprintf(stderr, "Unsupported version number (%d) on oper.db.\n"
			"Are you using a beta version of Epona?\n", ver);
	exit(1);
    }

    /* stats */
    SAFE(read_int32(&maxusercnt, f));
    SAFE(read_int32(&tmp32, f));
    maxusertime = tmp32;
    /* akills */
    SAFE(read_int16(&n, f));
    md = scalloc(sizeof(*md), n);
    for (i = 0; i < n; i++) {
	char *user, *host;
	SAFE(read_string(&user, f));
	SAFE(read_string(&host, f));
	s = smalloc(strlen(user)+strlen(host)+2);
	sprintf(s, "%s@%s", user, host);
	md[i].mask = s;
	SAFE(read_string(&s, f));
	strscpy(md[i].who, s, NICKMAX);
	SAFE(read_string(&md[i].reason, f));
	SAFE(read_int32(&tmp32, f));
	md[i].time = tmp32;
	SAFE(read_int32(&tmp32, f));
	md[i].expires = tmp32;
	if (md[i].mask)
	    add_maskdata(MD_AKILL, &md[i]);
    }
    /* sglines */
    SAFE(read_int16(&n, f));
    md = scalloc(sizeof(*md), n);
    for (i = 0; i < n; i++) {
	SAFE(read_string(&md[i].mask, f));
	SAFE(read_string(&s, f));
	strscpy(md[i].who, s, NICKMAX);
	SAFE(read_string(&md[i].reason, f));
	SAFE(read_int32(&tmp32, f));
	md[i].time = tmp32;
	SAFE(read_int32(&tmp32, f));
	md[i].expires = tmp32;
	if (md[i].mask)
	    add_maskdata(MD_SGLINE, &md[i]);
    }
    if (ver >= 13) {
	/* sqlines */
	SAFE(read_int16(&n, f));
	md = scalloc(sizeof(*md), n);
	for (i = 0; i < n; i++) {
	    SAFE(read_string(&md[i].mask, f));
	    SAFE(read_string(&s, f));
	    strscpy(md[i].who, s, NICKMAX);
	    SAFE(read_string(&md[i].reason, f));
	    SAFE(read_int32(&tmp32, f));
	    md[i].time = tmp32;
	    SAFE(read_int32(&tmp32, f));
	    md[i].expires = tmp32;
	    if (md[i].mask)
		add_maskdata(MD_SQLINE, &md[i]);
	}
    }
    /* szlines */
    SAFE(read_int16(&n, f));
    md = scalloc(sizeof(*md), n);
    for (i = 0; i < n; i++) {
	SAFE(read_string(&md[i].mask, f));
	SAFE(read_string(&s, f));
	strscpy(md[i].who, s, NICKMAX);
	SAFE(read_string(&md[i].reason, f));
	SAFE(read_int32(&tmp32, f));
	md[i].time = tmp32;
	SAFE(read_int32(&tmp32, f));
	md[i].expires = tmp32;
	if (md[i].mask)
	    add_maskdata(MD_SZLINE, &md[i]);
    }
    if (ver >= 13) {
	/* proxy (open-relay) host cache */
	for (i = 0; i < 1024; i++) {
	    char c;
	    SAFE(read_int8(&c, f));
	    while (c) {
		SAFE(read_string(&s, f));  /* host */
		SAFE(read_int16(&tmp16, f));  /* status */
		SAFE(read_int32(&tmp32, f));  /* used */
		SAFE(read_int8(&c, f));
	    }
	}
    }

    close_db(f);
}

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

static void epona_load_exception(const char *sourcedir)
{
    dbFILE *f;
    int32 ver;
    int16 i, n;
    int32 tmp32;
    MaskData *md;

    f = open_db_ver(sourcedir, "exception.db", 9, 9, &ver);
    SAFE(read_int16(&n, f));
    md = scalloc(sizeof(*md), n);
    for (i = 0; i < n; i++) {
	char nickbuf[32];
	SAFE(read_string(&md[i].mask, f));
	SAFE(read_int16(&md[i].limit, f));
	SAFE(read_buffer(nickbuf, f));
	strscpy(md[i].who, nickbuf, NICKMAX);
	SAFE(read_string(&md[i].reason, f));
	SAFE(read_int32(&tmp32, f));
	md[i].time = tmp32;
	SAFE(read_int32(&tmp32, f));
	md[i].expires = tmp32;
	md[i].num = i;
	if (md[i].mask)
	    add_maskdata(MD_EXCEPTION, &md[i]);
    }
    close_db(f);
}

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

static void epona_load_news(const char *sourcedir)
{
    dbFILE *f;
    int32 ver;
    int16 i, n;
    int32 tmp32;
    NewsItem *news;

    f = open_db_ver(sourcedir, "news.db", 9, 9, &ver);
    SAFE(read_int16(&n, f));
    news = scalloc(sizeof(*news), n);
    for (i = 0; i < n; i++) {
	char nickbuf[32];
	SAFE(read_int16(&news[i].type, f));
	SAFE(read_int32(&news[i].num, f));
	SAFE(read_string(&news[i].text, f));
	SAFE(read_buffer(nickbuf, f));
	strscpy(news[i].who, nickbuf, NICKMAX);
	SAFE(read_int32(&tmp32, f));
	news[i].time = tmp32;
	add_news(&news[i]);
    }
    close_db(f);
}


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

static const char *check_epona(const char *sourcedir)
{
    char buf[PATH_MAX+1];

    snprintf(buf, sizeof(buf), "%s/bot.db", sourcedir);
    if (access(buf, R_OK) == 0) {
	FILE *f;
	snprintf(buf, sizeof(buf), "%s/chan.db", sourcedir);
	f = fopen(buf, "rb");
	if (f) {
	    int32 ver;
	    ver  = fgetc(f)<<24;
	    ver |= fgetc(f)<<16;
	    ver |= fgetc(f)<< 8;
	    ver |= fgetc(f);
	    switch (ver) {
		case 14: return "Epona 1.3.x";
		case 15: return "Epona 1.4.0";
		case 16: return "Epona 1.4.1+";
	    }
	}
    }
    return NULL;
}

static void load_epona(const char *sourcedir, int verbose)
{
    if (verbose)
	fprintf(stderr, "Loading nick.db...\n");
    epona_load_nick(sourcedir);
    if (verbose)
	fprintf(stderr, "Loading chan.db...\n");
    epona_load_chan(sourcedir);
    if (verbose)
	fprintf(stderr, "Loading oper.db...\n");
    epona_load_oper(sourcedir);
    if (verbose)
	fprintf(stderr, "Loading exception.db...\n");
    epona_load_exception(sourcedir);
    if (verbose)
	fprintf(stderr, "Loading news.db...\n");
    epona_load_news(sourcedir);
    if (verbose)
	fprintf(stderr, "Data files successfully loaded.\n");
}

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

DBTypeInfo dbtype_epona = {
    "epona",
    check_epona,
    load_epona
};

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