/* Conversion routines for PTlink >= 2.13.x databases.
 *
 * 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"

#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

/* Encryption method used for first seen password; used to ensure that all
 * passwords are encrypted with the same method */
static int16 encryption_method = -1;
/* Have we warned about different encryption methods? */
static int encryption_warned = 0;

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

static void ptlink_load_nick(const char *sourcedir)
{
    char filename[PATH_MAX+1];
    dbFILE *f;
    int i, j, c, delete;
    int16 tmp16, ver;
    int32 tmp32, total, count;
    NickInfo *ni;
    NickGroupInfo *ngi;
    char *s;
    signed char ch;

    snprintf(filename, sizeof(filename), "%s/nick.db", sourcedir);
    if (!(f = open_db(filename, "r", 0)))
	return;
    SAFE(read_int8(&ch, f));
    SAFE(read_int16(&ver, f));
    if (ch != -1 || ver < 6 || ver > 11) {
	fprintf(stderr, "Wrong version number on %s\n", filename);
	exit(1);
    }

    SAFE(read_int32(&total, f));
    count = 0;
#ifdef CLEAN_COMPILE
    c = 0;
#endif
    for (i = 0; i < 256; i++) {
	while (ver >= 10 || (c = getc_db(f)) == 1) {
	    char nickbuf[32], passbuf[32], authbuf[16];
	    if (ver >= 9) {
		int res = read_int32(&tmp32, f);  /* SUID (broken) / SNID */
		if (res < 0) {
		    if (ver >= 10)  /* v10: no more int8 markers */
			break;
		    else
			SAFE(res);
		}
	    }
	    SAFE(read_buffer(nickbuf, f));
	    /* The source (v2.26-eol.1) claims that duplicate nicknames can
	     * get stored in the database for unknown reasons, so watch for
	     * and delete them as needed */
	    delete = (get_nickinfo(nickbuf) != NULL);
	    ni = makenick(nickbuf, &ngi);
	    /* Use the SNID (file version 11) as a nickname group ID only
	     * if it's nonzero and unique.  Never use the SUID (versions 9
	     * and 10) as the source code claims that it's broken. */
	    if (ver >= 11 && tmp32 && !get_nickgroupinfo(tmp32))
		ni->nickgroup = ngi->id = (uint32)tmp32;
	    SAFE(read_buffer(passbuf, f));
	    memcpy(ngi->pass, passbuf, sizeof(passbuf));
	    if (ver >= 7)
		SAFE(read_buffer(authbuf, f));
	    SAFE(read_string(&ngi->url, f));
	    if (ver >= 7)
		SAFE(read_string(&s, f));  /* temporary, unauthed E-mail */
	    SAFE(read_string(&ngi->email, f));
	    SAFE(read_string(&s, f));  /* icq_number */
	    SAFE(read_string(&s, f));  /* location */
	    SAFE(read_string(&ni->last_usermask, f));
	    if (!ni->last_usermask)
		ni->last_usermask = (char *)"@";
	    SAFE(read_string(&ni->last_realname, f));
	    if (!ni->last_realname)
		ni->last_realname = (char *)"";
	    SAFE(read_string(&ni->last_quit, f));
	    SAFE(read_int32(&tmp32, f));
	    ni->time_registered = tmp32;
	    SAFE(read_int32(&tmp32, f));  /* last_identify */
	    SAFE(read_int32(&tmp32, f));
	    ni->last_seen = tmp32;
	    if (ver >= 7)
		SAFE(read_int32(&tmp32, f));  /* last_email_request */
	    SAFE(read_int32(&tmp32, f));  /* birth_date */
	    SAFE(read_int16(&tmp16, f));  /* status */
	    if (tmp16 & 0x0002) {
		ni->status |= NS_VERBOTEN;
		del_nickgroupinfo(ngi);
		ni->nickgroup = 0;
	    }
	    if (tmp16 & 0x0004)
		ni->status |= NS_NOEXPIRE;
	    SAFE(read_int16(&tmp16, f));  /* crypt_method */
	    if (encryption_method >= 0) {
		if (tmp16 != encryption_method && !encryption_warned) {
		    fprintf(stderr, "Warning: mixed encryption methods in"
			    " use; some passwords may not be usable.\n");
		    encryption_warned = 1;
		}
	    } else {
		encryption_method = tmp16;
	    }
	    if (tmp16 == 3) {
		static int warned = 0;
		if (!warned) {
		    fprintf(stderr,
			    "Warning: passwords are encrypted; be sure to"
			    " be sure to enable the MD5\n"
			    "         encryption module in your Services"
			    " configuration.\n");
		    warned = 1;
		}
	    } else if (tmp16 != 0) {
		fprintf(stderr, "%s: password encrypted with unsupported"
				" method %d, aborting\n", filename, tmp16);
		exit(1);
	    }
	    SAFE(read_int32(&tmp32, f));  /* news_mask */
	    SAFE(read_int16(&tmp16, f));  /* news_status */
	    SAFE(read_string(&ni->last_realmask, f));  /* link */
	    SAFE(read_int16(&tmp16, f));  /* linkcount */
	    if (ni->last_realmask) {
		ni->nickgroup = 0;
		SAFE(read_int16(&tmp16, f));  /* channelcount */
	    } else {
		SAFE(read_int32(&tmp32, f));  /* flags */
		if (tmp32 & 0x00000001)
		    ngi->flags |= NF_KILLPROTECT;
		if (tmp32 & 0x00004002)
		    ngi->flags |= 0x80000000;  /* suspended */
		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;
		SAFE(read_int32(&tmp32, f));  /* online */
		if (ngi->flags & 0x80000000) {
		    ngi->flags &= ~0x80000000;
		    SAFE(read_int32(&tmp32, f));  /* expires */
		    ngi->suspendinfo = new_suspendinfo(
			    "<unknown>",
			    "Unknown (imported from PTlink IRC Services)",
			    tmp32
		    );
		}
		SAFE(read_int16(&ngi->ajoin_count, f));
		ngi->ajoin = scalloc(sizeof(char *), ngi->ajoin_count);
		for (j = 0; j < ngi->ajoin_count; j++)
		    SAFE(read_string(&ngi->ajoin[j], 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(*memos), ngi->memos.memos_count);
		    ngi->memos.memos = memos;
		    for (j = 0; j < ngi->memos.memos_count; j++, memos++) {
			SAFE(read_uint32(&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));  /* notes.count */
		j = tmp16;
		SAFE(read_int16(&tmp16, f));  /* notes.max */
		while (j--)  /* notes.note[] */
		    SAFE(read_string(&s, f));
		SAFE(read_int16(&tmp16, f));  /* channelcount */
		SAFE(read_int16(&tmp16, f));  /* channelmax */
		SAFE(read_int16(&tmp16, f));
		switch (tmp16) {
		    case 0:  ngi->language = LANG_EN_US;   break;
		    case 1:  ngi->language = LANG_PT;      break;
		    case 2:  ngi->language = LANG_TR;      break;
		    case 3:  ngi->language = LANG_DE;      break;
		    case 4:  ngi->language = LANG_IT;      break;
		}
	    }
	    if (delete) {
		del_nickgroupinfo(ngi);
		del_nickinfo(ni);
	    }
	    count++;
	} /* while (getc_db(f) == 1) */
	if (ver < 10 && c != 0) {
	    fprintf(stderr, "%s is corrupt, aborting.\n", f->filename);
	    exit(1);
	}
    } /* for (i) */
    close_db(f);
    if (count != total) {
	fprintf(stderr, "%s: warning: expected %d nicks, got %d\n",
		filename, total, count);
	fprintf(stderr, "    This means that your data files may be corrupt."
		"  It may also be the\n"
		"    result of a bug in some versions of PTlink Services.\n");
    }

    /* Resolve links */
    for (ni = first_nickinfo(); ni; ni = next_nickinfo()) {
	NickInfo *ni2;
	const char *last_nick;
	if (ni->last_realmask) {
	    ni2 = ni;
	    /* Find root nick (this will actually stop at the first nick
	     * in the path to the root that isn't marked as linked, but
	     * that's okay because such a nick will already have its
	     * nickgroup ID set correctly) */
	    do {
		last_nick = ni2->last_realmask;
		ni2 = get_nickinfo(last_nick);
	    } while (ni2 && ni2 != ni && ni2->last_realmask);
	    ni->last_realmask = NULL;
	    /* Set nickgroup, or delete nick if an error occurred */
	    if (ni2 == ni) {
		fprintf(stderr,
			"Warning: dropping nick %s with circular link\n",
			ni->nick);
		del_nickinfo(ni);
	    } else if (!ni2) {
		fprintf(stderr, "Warning: dropping nick %s linked to"
			" nonexistent nick %s\n", ni->nick, last_nick);
		del_nickinfo(ni);
	    } else {
		ngi = get_nickgroupinfo(ni->nickgroup);
		if (ngi)
		    del_nickgroupinfo(ngi);
		ni->nickgroup = ni2->nickgroup;
		ngi = get_nickgroupinfo(ni->nickgroup);
		if (ngi) {
		    ARRAY_EXTEND(ngi->nicks);
		    strscpy(ngi->nicks[ngi->nicks_count-1], ni->nick, NICKMAX);
		} else if (ni->nickgroup != 0) {
		    fprintf(stderr, "Warning: Nick group %d for nick %s not"
			    " found -- program bug?  Output may be corrupt.",
			    ni->nickgroup, ni->nick);
		}
	    }
	}
    }
}

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

static struct {
    int32 flag;
    char mode;
} ptlink_cmodes[] = {
    { 0x00000001, 'i' },
    { 0x00000002, 'm' },
    { 0x00000004, 'n' },
    { 0x00000008, 'p' },
    { 0x00000010, 's' },
    { 0x00000020, 't' },
    { 0x00000040, 'k' },
    { 0x00000080, 'l' },
    { 0x00000200, 'R' },
    { 0x00000400, 'c' },
    { 0x00001000, 'O' },
    { 0x00002000, 'A' },
    { 0x00008000, 0   }, /* Like Unreal 'f': no dup lines within 10 secs */
    { 0, 0 }
};

static void ptlink_load_chan(const char *sourcedir)
{
    char filename[PATH_MAX+1];
    dbFILE *f;
    int i, j, c, C_MAX;
    ChannelInfo *ci;
    NickInfo *ni;
    signed char ch;
    int16 tmp16, ver;
    int32 tmp32, total, count, mlock_on, mlock_off;
    char *on, *off;

    snprintf(filename, sizeof(filename), "%s/chan.db", sourcedir);
    if (!(f = open_db(filename, "r", 0)))
	return;
    SAFE(read_int8(&ch, f));
    SAFE(read_int16(&ver, f));
    if (ch != -1 || ver < 7 || ver > 10) {
	fprintf(stderr, "Wrong version number on %s\n", filename);
	exit(1);
    }

    if (ver >= 9)
	SAFE(read_int32(&total, f));
    count = 0;

    C_MAX = (ver<9 ? 256 : 65535);
    for (i = 0; i < C_MAX; i++) {
	char *s;

	while ((c = getc_db(f)) == 1) {
	    char namebuf[64], passbuf[32], nickbuf[32];
	    if (ver >= 10)
		SAFE(read_int32(&tmp32, f));  /* SCID */
	    SAFE(read_buffer(namebuf, f));
	    ci = makechan(namebuf);
	    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_int16(&tmp16, f));  /* maxusers */
	    SAFE(read_int32(&tmp32, f));  /* maxtime */
	    SAFE(read_buffer(passbuf, f));
	    memcpy(ci->founderpass, passbuf, sizeof(passbuf));
	    SAFE(read_string(&ci->desc, f));
	    if (!ci->desc)
		ci->desc = (char *)"";
	    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 & 0x00000020)
		ci->flags |= CI_LEAVEOPS;
	    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_int16(&tmp16, f));  /* crypt_method */
	    if (encryption_method >= 0) {
		if (tmp16 != encryption_method && !encryption_warned) {
		    fprintf(stderr, "Warning: mixed encryption methods in"
			    " use; some passwords may not be usable.\n");
		    encryption_warned = 1;
		}
	    } else {
		encryption_method = tmp16;
	    }
	    if (tmp16 != 0 && tmp16 != 3) {
		fprintf(stderr, "%s: password encrypted with unsupported"
				" method %d, aborting\n", filename, tmp16);
		exit(1);
	    }
	    if (tmp32 & 0x1000)
		SAFE(read_int32(&tmp32, f));  /* drop_time */

	    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));
			ci->access[j].level =
			    convert_acclev(ci->access[j].level);
			if (s) {
			    ni = get_nickinfo(s);
			    if (ni)
				ci->access[j].nickgroup = ni->nickgroup;
			}
			SAFE(read_string(&s, f));  /* who */
		    }
		}
	    }

	    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++) {
		    SAFE(read_int16(&tmp16, f));  /* in_use */
		    if (tmp16) {
			SAFE(read_string(&ci->akick[j].mask, f));
			SAFE(read_string(&ci->akick[j].reason, f));
			SAFE(read_string(&s, f));	/* who */
			strscpy(ci->akick[j].who, s, NICKMAX);
			ci->akick[j].set = time(NULL);
			SAFE(read_int32(&tmp32, f));	/* last_kick */
			ci->akick[j].lastused = tmp32;
		    }
		}
	    }

	    if (ver < 8) {
		SAFE(read_int16(&tmp16, f));
		mlock_on = (int32)tmp16 & 0xFFFF;
		SAFE(read_int16(&tmp16, f));
		mlock_off = (int32)tmp16 & 0xFFFF;
	    } else {
		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; ptlink_cmodes[j].flag != 0; j++) {
		if (mlock_on & ptlink_cmodes[j].flag)
		    *on++ = ptlink_cmodes[j].mode;
		if (mlock_off & ptlink_cmodes[j].flag)
		    *off++ = ptlink_cmodes[j].mode;
	    }
	    *on = 0;
	    *off = 0;
	    SAFE(read_int32(&ci->mlock_limit, f));
	    SAFE(read_string(&ci->mlock_key, 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_uint32(&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));

	    if ((ci->flags & CI_VERBOTEN) || ci->founder)
		add_channelinfo(ci);

	    count++;

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

    if (ver >= 9 && count != total) {
	fprintf(stderr, "%s: warning: expected %d channels, got %d\n",
		filename, total, count);
	fprintf(stderr, "    This means that your data files may be corrupt."
		"  It may also be the\n"
		"    result of a bug in some versions of PTlink Services.\n");
    }
}

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

static void ptlink_load_oper(const char *sourcedir)
{
    char filename[PATH_MAX+1];
    dbFILE *f;
    int16 i, n, tmp16;
    int32 tmp32;
    char *s;
    signed char ch;

    snprintf(filename, sizeof(filename), "%s/oper.db", sourcedir);
    if (!(f = open_db(filename, "r", 0)))
	return;
    SAFE(read_int8(&ch, f));
    SAFE(read_int16(&tmp16, f));
    if (ch != -1 || tmp16 < 1 || tmp16 > 2) {
	fprintf(stderr, "Wrong version number on %s\n", filename);
	exit(1);
    }
    SAFE(read_int16(&n, f));
    for (i = 0; i < n; i++) {
	SAFE(read_string(&s, f));
	set_os_priv(s, NP_SERVADMIN);
    }
    SAFE(read_int16(&n, f));
    for (i = 0; i < n; i++) {
	SAFE(read_string(&s, f));
	set_os_priv(s, NP_SERVOPER);
    }
    SAFE(read_int32(&maxusercnt, f));
    SAFE(read_int32(&tmp32, f));
    maxusertime = tmp32;
    close_db(f);
}

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

static void ptlink_load_akill(const char *sourcedir)
{
    char filename[PATH_MAX+1];
    dbFILE *f;
    int16 tmp16, i, n;
    int32 tmp32;
    signed char ch;
    MaskData *md;

    snprintf(filename, sizeof(filename), "%s/akill.db", sourcedir);
    if (!(f = open_db(filename, "r", 0)))
	return;
    SAFE(read_int8(&ch, f));
    SAFE(read_int16(&tmp16, f));
    if (ch != -1 || tmp16 != 1) {
	fprintf(stderr, "Wrong version number on %s\n", filename);
	exit(1);
    }
    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_string(&md[i].reason, f));
	SAFE(read_buffer(nickbuf, f));
	strscpy(md[i].who, nickbuf, NICKMAX);
	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]);
    }
    close_db(f);
}

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

static void ptlink_load_news(const char *sourcedir)
{
    char filename[PATH_MAX+1];
    dbFILE *f;
    int16 tmp16, i, n;
    int32 tmp32;
    signed char ch;
    NewsItem *news;

    snprintf(filename, sizeof(filename), "%s/news.db", sourcedir);
    if (!(f = open_db(filename, "r", 0)))
	return;
    SAFE(read_int8(&ch, f));
    SAFE(read_int16(&tmp16, f));
    if (ch != -1 || tmp16 != 1) {
	fprintf(stderr, "Wrong version number on %s\n", filename);
	exit(1);
    }
    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_ptlink(const char *sourcedir)
{
    char buf[PATH_MAX+1];

    snprintf(buf, sizeof(buf), "%s/vline.db", sourcedir);
    if (access(buf, R_OK) == 0)
	return "PTlink";
    return NULL;
}

static void load_ptlink(const char *sourcedir, int verbose, int ac, char **av)
{
    if (ac > 1) {
	fprintf(stderr, "Unrecognized option %s\n", av[1]);
	usage(av[0]);
    }
    if (verbose)
	fprintf(stderr, "Loading nick.db...\n");
    ptlink_load_nick(sourcedir);
    if (verbose)
	fprintf(stderr, "Loading chan.db...\n");
    ptlink_load_chan(sourcedir);
    if (verbose)
	fprintf(stderr, "Loading oper.db...\n");
    ptlink_load_oper(sourcedir);
    if (verbose)
	fprintf(stderr, "Loading akill.db...\n");
    ptlink_load_akill(sourcedir);
    if (verbose)
	fprintf(stderr, "Loading news.db...\n");
    ptlink_load_news(sourcedir);
}

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

DBTypeInfo dbtype_ptlink = {
    "ptlink",
    check_ptlink,
    load_ptlink
};

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