/*
 * ==================================================================
 * Filename:             m_staff.c
 * Description:          Command /staff
 * Written by:           AngryWolf <angrywolf@flashmail.com>
 * Documentation:        m_staff.txt (comes with the package)
 * ==================================================================
 */

#include "config.h"
#include "struct.h"
#include "common.h"
#include "sys.h"
#include "numeric.h"
#include "msg.h"
#include "channel.h"
#include <time.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <io.h>
#endif
#include <fcntl.h>
#include "h.h"
#ifdef STRIPBADWORDS
#include "badwords.h"
#endif
#ifdef _WIN32
#include "version.h"
#endif

extern void		sendto_one(aClient *to, char *pattern, ...);
extern void		sendto_realops(char *pattern, ...);
extern ConfigEntry	*config_find_entry(ConfigEntry *, char *);
#ifdef USE_LIBCURL
extern char		*download_file(char *url, char **error);
extern void		download_file_async(char *url, time_t cachetime, vFP callback);
extern char		*url_getfilename(char *url);
extern int		url_is_valid(char *string);
#endif

#define MSG_STAFF	"STAFF"
#define TOK_STAFF	"ST"

#define DEF_STAFF_FILE	"network.staff"
#define CONF_STAFF_FILE	(staff_file ? staff_file : DEF_STAFF_FILE)
#ifdef USE_LIBCURL
#define STAFF_FILE	(Download.path ? Download.path : CONF_STAFF_FILE)
#else
#define STAFF_FILE	CONF_STAFF_FILE
#endif

#define RPL_STAFF	":%s 700 %s :- %s"
#define RPL_STAFFSTART	":%s 701 %s :- %s IRC Network Staff Information -"
#define RPL_ENDOFSTAFF	":%s 702 %s :End of /STAFF command."
#define RPL_NOSTAFF	":%s 703 %s :Network Staff File is missing"

#define DelHook(x)	if (x) HookDel(x); x = NULL
#define DelCommand(x)	if (x) CommandDel(x); x = NULL
#define ircstrdup(x,y)  if (x) MyFree(x); if (!y) x = NULL; else x = strdup(y)
#define ircfree(x)	if (x) MyFree(x); x = NULL

static Command		*AddCommand(Module *module, char *msg, char *token, iFP func);
static void		unload_motd_file(aMotd **list);
static int		m_staff(aClient *cptr, aClient *sptr, int parc, char *parv[]);
#ifdef HOOKTYPE_PRE_LOCAL_PART
static int		cb_rehashflag(aClient *cptr, aClient *sptr, char *flag);
#else
static int		cb_rehashflag(char *flag);
#endif
static int		cb_test(ConfigFile *, ConfigEntry *, int, int *);
static int		cb_conf(ConfigFile *, ConfigEntry *, int);
static int		cb_rehash();
static int		cb_stats(aClient *sptr, char *flag);
#ifdef USE_LIBCURL
static int		download_staff_file(ConfigEntry *ce);
static void		download_staff_file_complete(char *url, char *file, char *errorbuf, int cached);
#endif
static void		InitConf();
static void		FreeConf();

Command			*CmdStaff;
Hook			*HookConfTest, *HookConfRun, *HookConfRehash, *HookStats;
Hook			*HookRehashFlag;
static aMotd		*staff;
static char		*staff_file;

#ifdef USE_LIBCURL
struct {
	unsigned	is_url : 1;
	unsigned	once_completed : 1;
	unsigned	in_progress : 1;
	char		*file;			// File name
	char		*path;			// File path
	char		*url;			// Full URL address
} Download;
#endif

ModuleHeader MOD_HEADER(m_staff)
  = {
	"staff",
	"$Id: m_staff.c,v 2.5 2004/03/27 18:04:51 angrywolf Exp $",
	"command /staff",
	"3.2-b8-1",
	NULL 
    };

DLLFUNC int MOD_TEST(m_staff)(ModuleInfo *modinfo)
{
	HookConfTest = HookAddEx(modinfo->handle, HOOKTYPE_CONFIGTEST, cb_test);

	return MOD_SUCCESS;
}

DLLFUNC int MOD_INIT(m_staff)(ModuleInfo *modinfo)
{
#ifdef USE_LIBCURL
	memset(&Download, 0, sizeof Download);
 #ifndef STATIC_LINKING
	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM);
 #endif
#endif
	InitConf();

	CmdStaff	= AddCommand(modinfo->handle, MSG_STAFF, TOK_STAFF, m_staff);
	HookConfRun	= HookAddEx(modinfo->handle, HOOKTYPE_CONFIGRUN, cb_conf);
	HookConfRehash	= HookAddEx(modinfo->handle, HOOKTYPE_REHASH, cb_rehash);
	HookRehashFlag	= HookAddEx(modinfo->handle, HOOKTYPE_REHASHFLAG, cb_rehashflag);
	HookStats	= HookAddEx(modinfo->handle, HOOKTYPE_STATS, cb_stats);

	if (!CmdStaff)
		return MOD_FAILED;

	return MOD_SUCCESS;
}

DLLFUNC int MOD_LOAD(m_staff)(int module_load)
{
	return MOD_SUCCESS;
}

DLLFUNC int MOD_UNLOAD(m_staff)(int module_unload)
{
	FreeConf();
	unload_motd_file(&staff);
	DelCommand(CmdStaff);

	DelHook(HookStats);
	DelHook(HookRehashFlag);
	DelHook(HookConfRehash);
	DelHook(HookConfRun);
	DelHook(HookConfTest);

#ifdef USE_LIBCURL
	ircfree(Download.path);
    	ircfree(Download.file);
	ircfree(Download.url);
#endif

	return MOD_SUCCESS;
}

static int cb_rehash()
{
	FreeConf();
	InitConf();

	return 1;
}

static void InitConf()
{
	staff_file = NULL;
}

static void FreeConf()
{
	if (staff_file)
		MyFree(staff_file);
}

static Command *AddCommand(Module *module, char *msg, char *token, iFP func)
{
	Command *cmd;

	if (CommandExists(msg))
    	{
		config_error("Command %s already exists", msg);
		return NULL;
    	}
    	if (CommandExists(token))
	{
		config_error("Token %s already exists", token);
		return NULL;
    	}

	cmd = CommandAdd(module, msg, token, func, MAXPARA, 0);

#ifndef STATIC_LINKING
	if (ModuleGetError(module) != MODERR_NOERROR || !cmd)
#else
	if (!cmd)
#endif
	{
#ifndef STATIC_LINKING
		config_error("Error adding command %s: %s", msg,
			ModuleGetErrorStr(module));
#else
		config_error("Error adding command %s", msg);
#endif
		return NULL;
	}

	return cmd;
}

#ifdef USE_LIBCURL
static void remove_staff_file()
{
	if (Download.path)
	{
		if (remove(Download.path) == -1)
		{
			if (config_verbose > 0)
				config_status("Cannot remove file %s: %s",
					Download.path, strerror(errno));
		}
	        MyFree(Download.path);
	        Download.path = NULL;
	}
}

static int download_staff_file(ConfigEntry *ce)
{
	int		ret = 0;
	struct stat	sb;
	char		*file, *filename;

	if (Download.in_progress)
		return 0;

	Download.is_url = 1;
	ircstrdup(Download.url, ce->ce_vardata);

	file = url_getfilename(ce->ce_vardata);
	filename = unreal_getfilename(file);
	ircstrdup(Download.file, filename);
	MyFree(file);

	if (!loop.ircd_rehashing && !Download.once_completed)
	{
		char *error;

		if (config_verbose > 0)
			config_status("Downloading %s", Download.url);

		if (!(file = download_file(ce->ce_vardata, &error)))
		{
			config_error("%s:%i: test: error downloading '%s': %s",
				ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
				ce->ce_vardata, error);
			return -1;
		}

		Download.once_completed = 1;
		ircstrdup(Download.path, file);
		staff = (aMotd *) read_file(Download.path, &staff);

		MyFree(file);
		return 0;
	}

	file = Download.path ? Download.path : Download.file;

	if ((ret = stat(file, &sb)) && errno != ENOENT)
	{
		/* I know, stat shouldn't fail... */
		config_error("%s:%i: could not get the creation time of %s: stat() returned %d: %s",
			ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
			Download.file, ret, strerror(errno));
		return -1;
	}

	if (config_verbose > 0)
		config_status("Downloading %s", Download.url);

	Download.in_progress = 1;
	download_file_async(Download.url, sb.st_ctime, download_staff_file_complete);
	return 0;
}

static void download_staff_file_complete(char *url, char *file, char *errorbuf, int cached)
{
	Download.in_progress = 0;
	Download.once_completed = 1;

	if (!cached)
	{
		if (!file)
		{
			config_error("Error downloading %s: %s",
				url, errorbuf);
			return;
		}

		remove_staff_file();
		Download.path = strdup(file);
		staff = (aMotd *) read_file(Download.path, &staff);
	}
	else
        {
                char *urlfile = url_getfilename(url);
                char *file = unreal_getfilename(urlfile);
                char *tmp = unreal_mktemp("tmp", file);

                unreal_copyfile(Download.path, tmp);
		remove_staff_file();
		Download.path = strdup(tmp);
                MyFree(urlfile);
        }
}
#endif

static void unload_motd_file(aMotd **list)
{
	aMotd *old;

	if (!list)
		return;

        while (*list)
        {
                old = (*list)->next;
                MyFree((*list)->line);
                MyFree(*list);
                *list  = old;
        }
}

#define CHECK_EMPTY(ce, parent) \
		if (!(ce)->ce_varname) \
		{ \
			config_error("%s:%i: blank %s item", \
				(ce)->ce_fileptr->cf_filename, \
				(ce)->ce_varlinenum, (parent)->ce_varname); \
			errors++; \
			continue; \
		} \
		if (!(ce)->ce_vardata) \
		{ \
			config_error("%s:%i: %s::%s without value", \
				(ce)->ce_fileptr->cf_filename, \
				(ce)->ce_varlinenum, \
				(parent)->ce_varname, (ce)->ce_varname); \
			errors++; \
			continue; \
		}

static int cb_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
	int		errors = 0;
#ifdef USE_LIBCURL
	char		*file = NULL, *filename = NULL;
#endif

	if (type == CONFIG_SET)
	{
		if (!strcmp(ce->ce_varname, "staff-file"))
		{
#ifdef USE_LIBCURL
			if (url_is_valid(ce->ce_vardata))
			{
				if (!(file = url_getfilename(ce->ce_vardata)) || !(filename = unreal_getfilename(file)))
				{
					config_error("%s:%i: invalid filename in URL",
						ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
					errors++;
				}
				ircfree(file);
			}
#endif

			*errs = errors;
			return errors ? -1 : 1;
		}
	}

	return 0;
}

static int cb_conf(ConfigFile *cf, ConfigEntry *ce, int type)
{
	if (type == CONFIG_SET)
	{
		if (!strcmp(ce->ce_varname, "staff-file"))
		{
#ifdef USE_LIBCURL
			if (!Download.in_progress)
			{
				ircstrdup(staff_file, ce->ce_vardata);
				if (url_is_valid(ce->ce_vardata))
					download_staff_file(ce);
				else
#endif
					staff = (aMotd *) read_file(CONF_STAFF_FILE, &staff);
#ifdef USE_LIBCURL
			}

#endif
			return 1;
		}
	}

	return 0;
}

static int cb_stats(aClient *sptr, char *flag)
{
	if (*flag == 'S')
		sendto_one(sptr, ":%s %i %s :staff-file: %s",
			me.name, RPL_TEXT, sptr->name, STAFF_FILE);

	return 0;
}

#ifdef HOOKTYPE_PRE_LOCAL_PART
static int cb_rehashflag(aClient *cptr, aClient *sptr, char *flag)
#else
static int cb_rehashflag(char *flag)
#endif
{
	int myflag = 0;

	/* "-all" only keeps compatibility with beta19 */
	if (!_match("-all", flag) || (myflag = !_match("-staff", flag)))
	{
		if (myflag)
#ifdef HOOKTYPE_PRE_LOCAL_PART
			sendto_ops("%sRehashing network staff file on the request of %s",
                                cptr != sptr ? "Remotely " : "", sptr->name);
#else
			sendto_ops("Rehashing network staff file");
#endif

#ifdef USE_LIBCURL
		if (Download.is_url)
			staff = (aMotd *) read_file(Download.path, &staff);
		else
#endif
		staff = (aMotd *) read_file(CONF_STAFF_FILE, &staff);
	}

	return 0;
}

int m_staff(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
	aMotd *temp;

	if (!IsPerson(sptr))
		return -1;

	if (hunt_server_token(cptr, sptr, MSG_STAFF, TOK_STAFF, ":%s",
	    1, parc, parv) != HUNTED_ISME)
		return 0;

	if (staff == (aMotd *) NULL)
	{
		sendto_one(sptr, RPL_NOSTAFF, me.name, sptr->name);
		return 0;
	}

	sendto_one(sptr, RPL_STAFFSTART, me.name, sptr->name, ircnetwork);

	for (temp = staff; temp; temp = temp->next)
                sendto_one(sptr, RPL_STAFF, me.name, sptr->name, temp->line);

        sendto_one(sptr, RPL_ENDOFSTAFF, me.name, sptr->name);
        return 0;
}
