/* Routines to export Services databases in XML format.
 *
 * 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 "services.h"
#include "modules.h"
#include "conffile.h"
#include "language.h"
#include "modules/nickserv/nickserv.h"
#include "modules/chanserv/chanserv.h"
#include "modules/memoserv/memoserv.h"
#include "modules/operserv/operserv.h"
#include "modules/operserv/maskdata.h"
#include "modules/operserv/news.h"
#include "modules/statserv/statserv.h"

#include "xml.h"

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

#ifndef CONVERT_DB
static Module *module;
#endif

/* Write a field (either string, password, long, or unsigned long) in XML.
 * `indent' is the indent string to prefix the output with.
 */
#define XML_PUT_STRING(indent,structure,field) do {		\
    if ((structure).field)					\
	writefunc(data, "%s<" #field ">%s</" #field ">\n", indent, \
		  xml_quotestr((structure).field));		\
} while (0)
#define XML_PUT_PASS(indent,structure,field)			\
    writefunc(data, "%s<" #field ">%s</" #field ">\n", indent,	\
	      xml_quotebuf((structure).field,PASSMAX));
#define XML_PUT_LONG(indent,structure,field)			\
    writefunc(data, "%s<" #field ">%ld</" #field ">\n", indent,	\
	      (long)(structure).field)
#define XML_PUT_ULONG(indent,structure,field)			\
    writefunc(data, "%s<" #field ">%lu</" #field ">\n", indent,	\
	      (unsigned long)(structure).field)

/* Write an array of strings (with corresponding count) in XML. */
#define XML_PUT_STRARR(indent,structure,field) do {		\
    int i;							\
    writefunc(data, "%s<" #field " count='%lu'>\n", indent,	\
	    (unsigned long)(structure).field##_count); 		\
    ARRAY_FOREACH (i, (structure).field) {			\
	writefunc(data, "%s\t<array-element>%s</array-element>\n", \
		  indent, xml_quotestr((structure).field[i]));	\
    }								\
    writefunc(data, "%s</" #field ">\n", indent);		\
} while (0)

/*************************************************************************/
/*************************** Internal routines ***************************/
/*************************************************************************/

/* Quote any special characters in the given buffer and return the result
 * in a static buffer.  Trailing null characters are removed.
 */

static char *xml_quotebuf(const char *buf_, int size)
{
    const unsigned char *buf = (unsigned char *)buf_;
    static char retbuf[BUFSIZE*6+1];
    uint32 i;
    char *d;

    d = retbuf;
    while (size > 0 && !buf[size-1])
	size--;
    for (i = 0; i < size; i++, buf++) {
	if (d - retbuf >= sizeof(retbuf)-6) {
#ifdef CONVERT_DB
	    fprintf(stderr, "warning: xml_quotebuf(%p,%d) result too long,"
		    " truncated", buf, size);
#else
	    module_log("warning: xml_quotebuf(%p,%d) result too long,"
		       " truncated", buf, size);
#endif
	    break;
	}
	if (*buf < 32 || *buf > 126) {
	    sprintf(d, "&#%u;", *buf);
	    if (*buf < 10)
		d += 4;
	    else if (*buf < 100)
		d += 5;
	    else
		d += 6;
	} else switch (*buf) {
	  case '<':
	    memcpy(d, "&lt;", 4);
	    d += 4;
	    break;
	  case '>':
	    memcpy(d, "&gt;", 4);
	    d += 4;
	    break;
	  case '&':
	    memcpy(d, "&amp;", 5);
	    d += 5;
	    break;
	  default:
	    *d++ = *buf;
	    break;
	}
    }
    *d = 0;
    return retbuf;
}


/* Do the same thing to a \0-terminated string. */

#define xml_quotestr(s)  xml_quotebuf(s, strlen(s))

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

/* Write a SuspendInfo structure to the output file. */

static void write_suspendinfo(xml_writefunc_t writefunc, void *data,
			      const SuspendInfo *si)
{
    writefunc(data, "\t\t<suspendinfo>\n");
    XML_PUT_STRING("\t\t\t", *si, who);
    XML_PUT_STRING("\t\t\t", *si, reason);
    XML_PUT_LONG  ("\t\t\t", *si, suspended);
    XML_PUT_LONG  ("\t\t\t", *si, expires);
    writefunc(data, "\t\t</suspendinfo>\n");
}

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

/* Write a MemoInfo structure to the output file. */

static void write_memoinfo(xml_writefunc_t writefunc, void *data,
			   const MemoInfo *mi)
{
    int i;
    writefunc(data, "\t\t<memoinfo>\n\t\t\t<memos count='%d'>\n", mi->memos_count);
    ARRAY_FOREACH (i, mi->memos) {
	writefunc(data, "\t\t\t\t<memo>\n");
	XML_PUT_LONG  ("\t\t\t\t\t", mi->memos[i], number);
	XML_PUT_LONG  ("\t\t\t\t\t", mi->memos[i], flags);
	XML_PUT_LONG  ("\t\t\t\t\t", mi->memos[i], time);
	XML_PUT_STRING("\t\t\t\t\t", mi->memos[i], sender);
	XML_PUT_STRING("\t\t\t\t\t", mi->memos[i], text);
	writefunc(data, "\t\t\t\t</memo>\n");
    }
    writefunc(data, "\t\t\t</memos>\n");
    XML_PUT_LONG  ("\t\t\t", *mi, memomax);
    writefunc(data, "\t\t</memoinfo>\n");
}

/*************************************************************************/
/*************************** Database output *****************************/
/*************************************************************************/

/* Write all (major) Services constants to the file. */

static int export_constants(xml_writefunc_t writefunc, void *data)
{
    writefunc(data, "\t<constants>\n");
#define XML_PUT_CONST(c) \
writefunc(data, "\t\t<" #c ">%ld</" #c ">\n", (long)c)
    XML_PUT_CONST(LANG_DEFAULT);
    XML_PUT_CONST(CHANMAX_UNLIMITED);
    XML_PUT_CONST(CHANMAX_DEFAULT);
    XML_PUT_CONST(TIMEZONE_DEFAULT);
    XML_PUT_CONST(ACCLEV_FOUNDER);
    XML_PUT_CONST(ACCLEV_INVALID);
    XML_PUT_CONST(ACCLEV_SOP);
    XML_PUT_CONST(ACCLEV_AOP);
    XML_PUT_CONST(ACCLEV_HOP);
    XML_PUT_CONST(ACCLEV_VOP);
    XML_PUT_CONST(MEMOMAX_UNLIMITED);
    XML_PUT_CONST(MEMOMAX_DEFAULT);
    XML_PUT_CONST(NEWS_LOGON);
    XML_PUT_CONST(NEWS_OPER);
    XML_PUT_CONST(MD_AKILL);
    XML_PUT_CONST(MD_EXCLUSION);
    XML_PUT_CONST(MD_EXCEPTION);
    XML_PUT_CONST(MD_SGLINE);
    XML_PUT_CONST(MD_SQLINE);
    XML_PUT_CONST(MD_SZLINE);
#undef XML_PUT_CONST
    writefunc(data, "\t</constants>\n");
    return 1;
}

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

static int export_operserv_data(xml_writefunc_t writefunc, void *data)
{
    int32 maxusercnt;
    time_t maxusertime;
    char *supass;

    if (!get_operserv_data(OSDATA_MAXUSERCNT, &maxusercnt)
     || !get_operserv_data(OSDATA_MAXUSERTIME, &maxusertime)
     || !get_operserv_data(OSDATA_SUPASS, &supass))
	return 0;
    writefunc(data, "\t<maxusercnt>%d</maxusercnt>\n", maxusercnt);
    writefunc(data, "\t<maxusertime>%ld</maxusertime>\n", (long)maxusertime);
    if (supass) {
	writefunc(data, "\t<supass>%s</supass>\n",
		  xml_quotebuf(supass, PASSMAX));
    }
    return 1;
}

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

static int export_nick_db(xml_writefunc_t writefunc, void *data)
{
    NickInfo *ni;
    NickGroupInfo *ngi;

    for (ngi = first_nickgroupinfo(); ngi; ngi = next_nickgroupinfo()) {
	writefunc(data, "\t<nickgroupinfo>\n");
	XML_PUT_ULONG ("\t\t", *ngi, id);
	XML_PUT_STRARR("\t\t", *ngi, nicks);
	XML_PUT_ULONG ("\t\t", *ngi, mainnick);
	XML_PUT_PASS  ("\t\t", *ngi, pass);
	XML_PUT_STRING("\t\t", *ngi, url);
	XML_PUT_STRING("\t\t", *ngi, email);
	XML_PUT_STRING("\t\t", *ngi, info);
	XML_PUT_LONG  ("\t\t", *ngi, authcode);
	XML_PUT_LONG  ("\t\t", *ngi, authset);
	if (ngi->suspendinfo)
	    write_suspendinfo(writefunc, data, ngi->suspendinfo);
	XML_PUT_LONG  ("\t\t", *ngi, flags);
	XML_PUT_LONG  ("\t\t", *ngi, os_priv);
	XML_PUT_LONG  ("\t\t", *ngi, language);
	XML_PUT_LONG  ("\t\t", *ngi, timezone);
	XML_PUT_LONG  ("\t\t", *ngi, channelmax);
	XML_PUT_STRARR("\t\t", *ngi, access);
	XML_PUT_STRARR("\t\t", *ngi, ajoin);
	write_memoinfo(writefunc, data, &ngi->memos);
	XML_PUT_STRARR("\t\t", *ngi, ignore);
	writefunc(data, "\t</nickgroupinfo>\n");
    }
    for (ni = first_nickinfo(); ni; ni = next_nickinfo()) {
	writefunc(data, "\t<nickinfo>\n");
	XML_PUT_STRING("\t\t", *ni, nick);
	writefunc(data, "\t\t<status>%d</status>\n",
		  ni->status & ~NS_TEMPORARY);
	XML_PUT_STRING("\t\t", *ni, last_usermask);
	XML_PUT_STRING("\t\t", *ni, last_realmask);
	XML_PUT_STRING("\t\t", *ni, last_realname);
	XML_PUT_STRING("\t\t", *ni, last_quit);
	XML_PUT_LONG  ("\t\t", *ni, time_registered);
	XML_PUT_LONG  ("\t\t", *ni, last_seen);
	XML_PUT_ULONG ("\t\t", *ni, nickgroup);
	writefunc(data, "\t</nickinfo>\n");
    }
    return 1;
}

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

static int export_channel_db(xml_writefunc_t writefunc, void *data)
{
    int i;
    ChannelInfo *ci;

    for (ci = first_channelinfo(); ci; ci = next_channelinfo()) {
	writefunc(data, "\t<channelinfo>\n");
	XML_PUT_STRING("\t\t", *ci, name);
	XML_PUT_ULONG ("\t\t", *ci, founder);
	XML_PUT_ULONG ("\t\t", *ci, successor);
	XML_PUT_PASS  ("\t\t", *ci, founderpass);
	XML_PUT_STRING("\t\t", *ci, desc);
	XML_PUT_STRING("\t\t", *ci, url);
	XML_PUT_STRING("\t\t", *ci, email);
	XML_PUT_LONG  ("\t\t", *ci, time_registered);
	XML_PUT_LONG  ("\t\t", *ci, last_used);
	XML_PUT_STRING("\t\t", *ci, last_topic);
	XML_PUT_STRING("\t\t", *ci, last_topic_setter);
	XML_PUT_LONG  ("\t\t", *ci, last_topic_time);
	XML_PUT_LONG  ("\t\t", *ci, flags);
	if (ci->suspendinfo)
	    write_suspendinfo(writefunc, data, ci->suspendinfo);
	if (ci->levels) {
	    writefunc(data, "\t\t<levels>\n");
#define XML_PUT_LEVEL(lev) \
writefunc(data, "\t\t\t<" #lev ">%d</" #lev ">\n", ci->levels[lev])
	    XML_PUT_LEVEL(CA_INVITE);
	    XML_PUT_LEVEL(CA_AKICK);
	    XML_PUT_LEVEL(CA_SET);
	    XML_PUT_LEVEL(CA_UNBAN);
	    XML_PUT_LEVEL(CA_AUTOOP);
	    XML_PUT_LEVEL(CA_AUTODEOP);
	    XML_PUT_LEVEL(CA_AUTOVOICE);
	    XML_PUT_LEVEL(CA_OPDEOP);
	    XML_PUT_LEVEL(CA_ACCESS_LIST);
	    XML_PUT_LEVEL(CA_CLEAR);
	    XML_PUT_LEVEL(CA_NOJOIN);
	    XML_PUT_LEVEL(CA_ACCESS_CHANGE);
	    XML_PUT_LEVEL(CA_MEMO);
	    XML_PUT_LEVEL(CA_VOICE);
	    XML_PUT_LEVEL(CA_AUTOHALFOP);
	    XML_PUT_LEVEL(CA_HALFOP);
	    XML_PUT_LEVEL(CA_AUTOPROTECT);
	    XML_PUT_LEVEL(CA_PROTECT);
	    XML_PUT_LEVEL(CA_AUTOOWNER);
#undef XML_PUT_LEVEL
	    writefunc(data, "\t\t</levels>\n");
	}
	writefunc(data, "\t\t<chanaccesslist count='%d'>\n", ci->access_count);
	ARRAY_FOREACH (i, ci->access) {
	    writefunc(data, "\t\t\t<chanaccess>\n");
	    XML_PUT_ULONG ("\t\t\t\t", ci->access[i], nickgroup);
	    XML_PUT_LONG  ("\t\t\t\t", ci->access[i], level);
	    writefunc(data, "\t\t\t</chanaccess>\n");
	}
	writefunc(data, "\t\t</chanaccesslist>\n");
	writefunc(data, "\t\t<akicklist count='%d'>\n", ci->akick_count);
	ARRAY_FOREACH (i, ci->akick) {
	    writefunc(data, "\t\t\t<akick>\n");
	    XML_PUT_STRING("\t\t\t\t", ci->akick[i], mask);
	    XML_PUT_STRING("\t\t\t\t", ci->akick[i], reason);
	    XML_PUT_STRING("\t\t\t\t", ci->akick[i], who);
	    XML_PUT_LONG  ("\t\t\t\t", ci->akick[i], set);
	    XML_PUT_LONG  ("\t\t\t\t", ci->akick[i], lastused);
	    writefunc(data, "\t\t\t</akick>\n");
	}
	writefunc(data, "\t\t</akicklist>\n");
#ifdef CONVERT_DB
	XML_PUT_STRING("\t\t", *ci, mlock_on);
	XML_PUT_STRING("\t\t", *ci, mlock_off);
#else
	writefunc(data, "\t\t<mlock_on>%s</mlock_on>\n",
		  mode_flags_to_string(ci->mlock_on, MODE_CHANNEL));
	writefunc(data, "\t\t<mlock_off>%s</mlock_off>\n",
		  mode_flags_to_string(ci->mlock_off, MODE_CHANNEL));
#endif
	XML_PUT_LONG  ("\t\t", *ci, mlock_limit);
	XML_PUT_STRING("\t\t", *ci, mlock_key);
	XML_PUT_STRING("\t\t", *ci, mlock_link);
	XML_PUT_STRING("\t\t", *ci, mlock_flood);
	XML_PUT_LONG  ("\t\t", *ci, mlock_joindelay);
	XML_PUT_LONG  ("\t\t", *ci, mlock_joinrate1);
	XML_PUT_LONG  ("\t\t", *ci, mlock_joinrate2);
	XML_PUT_STRING("\t\t", *ci, entry_message);
	write_memoinfo(writefunc, data, &ci->memos);
	writefunc(data, "\t</channelinfo>\n");
    }
    return 1;
}

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

static int export_news_db(xml_writefunc_t writefunc, void *data)
{
    NewsItem *news;

    for (news = first_news(); news; news = next_news()) {
	writefunc(data, "\t<news>\n");
	XML_PUT_LONG  ("\t\t", *news, type);
	XML_PUT_LONG  ("\t\t", *news, num);
	XML_PUT_STRING("\t\t", *news, text);
	XML_PUT_STRING("\t\t", *news, who);
	XML_PUT_LONG  ("\t\t", *news, time);
	writefunc(data, "\t</news>\n");
    }
    return 1;
}

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

static int export_maskdata(xml_writefunc_t writefunc, void *data)
{
    int i;
    MaskData *md;

    for (i = 0; i < 256; i++) {
	for (md = first_maskdata(i); md; md = next_maskdata(i)) {
	    writefunc(data, "\t<maskdata type='%d'>\n", i);
	    XML_PUT_LONG  ("\t\t", *md, num);
	    XML_PUT_STRING("\t\t", *md, mask);
	    if (md->limit)
		XML_PUT_LONG("\t\t",*md, limit);
	    XML_PUT_STRING("\t\t", *md, reason);
	    XML_PUT_STRING("\t\t", *md, who);
	    XML_PUT_LONG  ("\t\t", *md, time);
	    XML_PUT_LONG  ("\t\t", *md, expires);
	    XML_PUT_LONG  ("\t\t", *md, lastused);
	    writefunc(data, "\t</maskdata>\n");
	}
    }
    return 1;
}

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

static int export_statserv_db(xml_writefunc_t writefunc, void *data)
{
    ServerStats *ss;

    for (ss = first_serverstats(); ss; ss = next_serverstats()) {
	writefunc(data, "\t<serverstats>\n");
	XML_PUT_STRING("\t\t", *ss, name);
	XML_PUT_LONG  ("\t\t", *ss, t_join);
	XML_PUT_LONG  ("\t\t", *ss, t_quit);
	XML_PUT_STRING("\t\t", *ss, quit_message);
	writefunc(data, "\t</serverstats>\n");
    }
    return 1;
}

/*************************************************************************/
/**************************** Global routines ****************************/
/*************************************************************************/

EXPORT_FUNC(xml_export)
int xml_export(xml_writefunc_t writefunc, void *data)
{
    writefunc(data, "<?xml version='1.0'?>\n<ircservices-db>\n");
    return export_constants(writefunc, data)
	&& export_operserv_data(writefunc, data)
	&& export_nick_db(writefunc, data)
	&& export_channel_db(writefunc, data)
	&& export_news_db(writefunc, data)
	&& export_maskdata(writefunc, data)
	&& export_statserv_db(writefunc, data)
	&& (writefunc(data, "</ircservices-db>\n"), 1);
}

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

#ifndef CONVERT_DB

/* Command-line option callback. */

static int do_command_line(const char *option, const char *value)
{
    FILE *f;

    if (!option || strcmp(option, "export") != 0)
	return 0;
    if (!value || !*value || strcmp(value,"-") == 0) {
	f = stdout;
    } else {
	f = fopen(value, "w");
	if (!f) {
	    perror(value);
	    return 2;
	}
    }
    if (!xml_export((xml_writefunc_t)fprintf, f))
	return 2;
    return 3;
}

#endif  /* CONVERT_DB */

/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/

#ifndef CONVERT_DB

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

const int32 module_version = MODULE_VERSION_CODE;

ConfigDirective module_config[] = {
    { NULL }
};

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

int init_module(Module *module_)
{
    module = module_;

    if (!add_callback(NULL, "command line", do_command_line)) {
	module_log("Unable to add callback");
	exit_module(0);
	return 0;
    }

    return 1;
}

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

int exit_module(int shutdown_unused)
{
#ifdef CLEAN_COMPILE
    shutdown_unused = shutdown_unused;
#endif

    return 1;
}

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

#endif  /* CONVERT_DB */
