#include "burp.h"
#include "prog.h"
#include "msg.h"
#include "lock.h"
#include "handy.h"
#include "status_client.h"

static char *get_config_path(void)
{
	static char path[256]="";
#ifdef HAVE_WIN32
	char *pfenv=NULL;

	// Burp used to always install to 'C:/Program Files/Burp/', but as
	// of 1.3.11, it changed to %PROGRAMFILES%. Still want the old way
	// to work though. So check %PROGRAMFILES% first, then fall back.
	if((pfenv=getenv("PROGRAMFILES")))
	{
		struct stat statp;
		snprintf(path, sizeof(path), "%s/Burp/burp.conf", pfenv);
		if(!lstat(path, &statp)
		  && !S_ISDIR(statp.st_mode))
			return path;
	}
	snprintf(path, sizeof(path), "C:/Program Files/Burp/burp.conf");
#else
	snprintf(path, sizeof(path), "%s", SYSCONFDIR "/burp.conf");
#endif
	return path;
}

static void usage_server(void)
{
#ifndef HAVE_WIN32
	printf("\nThe configuration file specifies whether burp runs in server or client mode.\n");
	printf("\nServer usage: %s [options]\n", progname());
	printf("\n");
	printf(" Options:\n");
	printf("  -a s          Run the status monitor.\n");
	printf("  -a S          Screen dump of the status monitor (for reporting).\n");
	printf("  -c <path>     Path to config file (default: %s).\n", get_config_path());
	printf("  -d <path>     a single client in the status monitor\n");
	printf("  -F            Stay in the foreground.\n");
	printf("  -g            Generate initial CA certificates and exit.\n");
	printf("  -h|-?         Print this text and exit.\n");
	printf("  -i            Print index of symbols and exit\n");
	printf("  -n            Do not fork any children (implies '-F').\n");
	printf("  -t            Dry-run to test config file syntax.\n");
	printf("  -v            Print version and exit.\n");
	printf("Options to use with '-a S':\n");
	printf("  -C <client>   Show a particular client\n");
	printf("  -b <number>   Show listable files in a particular backup (requires -C)\n");
	printf("  -z <file>     Dump a particular log file in a backup (requires -C and -b)\n");
	printf("  -d <path>     Show a particular path in a backup (requires -C and -b)\n");
	printf("\n");
#endif
}

static void usage_client(void)
{
	printf("\nClient usage: %s [options]\n", progname());
	printf("\n");
	printf(" Options:\n");
	printf("  -a <action>    The action can be one of the following.\n");
	printf("                  b: backup\n");
	printf("                  delete: delete\n");
	printf("                  e: estimate\n");
	printf("                  l: list (this is the default when an action is not given)\n");
	printf("                  L: long list\n");
	printf("                  r: restore\n");
	printf("                  t: timed backup\n");
	printf("                  T: check backup timer, but do not actually backup\n");
	printf("                  v: verify\n");
	printf("  -b <number>    Backup number (default: the most recent backup)\n");
	printf("  -c <path>      Path to config file (default: %s).\n", get_config_path());
	printf("  -d <directory> Directory to restore to, or directory to list\n");
	printf("  -f             Allow overwrite during restore.\n");
	printf("  -h|-?          Print this text and exit.\n");
	printf("  -i             Print index of symbols and exit\n");
	printf("  -r <regex>     Specify a regular expression.\n");
	printf("  -s <number>    Number of leading path components to strip during restore.\n");
	printf("  -j             Format long list as JSON.\n");
	printf("  -t             Dry-run to test config file syntax.\n");
	printf("  -v             Print version and exit.\n");
#ifndef HAVE_WIN32
	printf("  -x             Do not use the Windows VSS API when restoring.\n");
#endif
	printf("\n");
#ifndef HAVE_WIN32
	printf(" See http://burp.grke.net/ or the man page ('man burp') for usage examples\n");
	printf(" and additional configuration options.\n\n");
#else
	printf(" See http://burp.grke.net/ for usage examples and additional configuration\n");
	printf(" options.\n\n");
#endif
}

int reload(struct config *conf, const char *configfile, bool firsttime, int oldmax_children, int oldmax_status_children, int json)
{
	if(!firsttime) logp("Reloading config\n");

	init_config(conf);

	if(load_config(configfile, conf, TRUE)) return 1;

	/* change umask */
	umask(conf->umask);

	// Try to make JSON output clean.
	if(json) conf->log_to_stdout=0;

	// This will turn on syslogging which could not be turned on before
	// load_config.
	set_logfp(NULL, conf);

#ifndef HAVE_WIN32
	if(conf->mode==MODE_SERVER)
		setup_signals(oldmax_children, conf->max_children,
			oldmax_status_children, conf->max_status_children);
#endif

	// Do not try to change user or group after the first time.
	if(firsttime && chuser_and_or_chgrp(conf->user, conf->group))
		return 1;

	return 0;
}

static int replace_conf_str(const char *newval, char **dest)
{
	if(newval)
	{
		if(*dest) free(*dest);
		if(!(*dest=strdup(newval)))
		{
			log_out_of_memory(__FUNCTION__);
			return -1;
		}
	}
	return 0;
}

static void usage(void)
{
	usage_server();
	usage_client();
}

#if defined(HAVE_WIN32)
#define main BurpMain
#endif
int main (int argc, char *argv[])
{
	int ret=0;
	int option=0;
	int daemon=1;
	int forking=1;
	int gotlock=0;
	int strip=0;
	struct config conf;
	int forceoverwrite=0;
	enum action act=ACTION_LIST;
	const char *backup=NULL;
	const char *restoreprefix=NULL;
	const char *regex=NULL;
	const char *browsefile=NULL;
	const char *browsedir=NULL;
	FILE *fp=NULL;
	const char *configfile=get_config_path();
	const char *orig_client=NULL;
	// The orig_client is the original client that the normal client
	// would like to restore from.
#ifndef HAVE_WIN32
	const char *sclient=NULL; // Status monitor client to view.
	int generate_ca_only=0;
#endif
	int vss_restore=1;
	int json=0;
	int test_conf=0;

	init_log(argv[0]);

	while((option=getopt(argc, argv, "a:b:c:C:d:ghfFil:nr:s:vxjtz:?"))!=-1)
	{
		switch(option)
		{
			case 'a':
				if(!strncmp(optarg, "backup", 1))
					act=ACTION_BACKUP;
				else if(!strncmp(optarg, "timedbackup", 1))
					act=ACTION_BACKUP_TIMED;
				else if(!strncmp(optarg, "Timercheck", 1))
					act=ACTION_TIMER_CHECK;
				else if(!strncmp(optarg, "restore", 1))
					act=ACTION_RESTORE;
				else if(!strncmp(optarg, "verify", 1))
					act=ACTION_VERIFY;
				else if(!strncmp(optarg, "list", 1))
					act=ACTION_LIST;
				else if(!strncmp(optarg, "List", 1))
					act=ACTION_LONG_LIST;
				else if(!strncmp(optarg, "status", 1))
					act=ACTION_STATUS;
				else if(!strncmp(optarg, "Status", 1))
					act=ACTION_STATUS_SNAPSHOT;
				else if(!strncmp(optarg, "estimate", 1))
					act=ACTION_ESTIMATE;
				// Make the user spell out 'delete' so that it
				// is likely to be used accidently.
				else if(!strncmp(optarg,
				  "delete", strlen("delete")))
					act=ACTION_DELETE;
				else
				{
					usage();
					return 1;
				}
				break;
			case 'b':
				backup=optarg;
				break;
			case 'c':
				configfile=optarg;
				break;
			case 'C':
				orig_client=optarg;
#ifndef HAVE_WIN32
				sclient=optarg;
#endif
				break;
			case 'd':
				restoreprefix=optarg; // for restores
				browsedir=optarg; // for lists
				break;
			case 'f':
				forceoverwrite=1;
				break;
			case 'F':
				daemon=0;
				break;
			case 'g':
#ifndef HAVE_WIN32
				generate_ca_only=1;
#endif
				break;
			case 'i':
				print_all_cmds();
				return 0;
			case 'l':
				logp("-l <logfile> option obsoleted\n");
				break;
			case 'n':
				forking=0;
				break;
			case 'r':
				regex=optarg;
				break;
			case 's':
				strip=atoi(optarg);
				break;
			case 'v':
				printf("%s-%s\n", progname(), VERSION);
				return 0;
			case 'x':
				vss_restore=0;
				break;
			case 'j':
				json=1;
				break;
			case 't':
				test_conf=1;
				break;
			case 'z':
				browsefile=optarg;
				break;
			case 'h':
			case '?':
			default:
				usage();
				return 1;
				break;
		}
	}
	if(optind<argc)
	{
		usage();
		return 1;
	}

	if(reload(&conf, configfile,
	  1 /* first time */,
	  0 /* no oldmax_children setting */,
	  0 /* no oldmax_status_children setting */,
	  json)) return 1;

	/* Dry run to test config file syntax */
	if (test_conf) return 0;

	if((act==ACTION_RESTORE || act==ACTION_VERIFY) && !backup)
	{
		logp("No backup specified. Using the most recent.\n");
		backup="0";
	}

	if(act==ACTION_DELETE && !backup)
	{
		logp("No backup specified for deletion.\n");
		return 1;
	}

	if(conf.mode==MODE_CLIENT)
	{
		if(orig_client && *orig_client)
		{
			if(!(conf.orig_client=strdup(orig_client)))
			{
				log_out_of_memory(__FUNCTION__);
				return 1;
			}
		}
	}

	if(conf.mode==MODE_SERVER
	  && (act==ACTION_STATUS || act==ACTION_STATUS_SNAPSHOT))
	{
		// Server status mode needs to run without getting the lock.
	}
	else
	{
		if(get_lock(conf.lockfile))
		{
			logp("Could not get lockfile.\n");
			logp("Another process is probably running,\n");
			logp("or you do not have permissions to write to %s.\n",
				conf.lockfile);
			return 1;
		}
		gotlock++;
	}

	conf.overwrite=forceoverwrite;
	conf.strip=strip;
	conf.forking=forking;
	conf.daemon=daemon;
	if(replace_conf_str(backup, &(conf.backup))
	  || replace_conf_str(restoreprefix, &(conf.restoreprefix))
	  || replace_conf_str(regex, &(conf.regex))
	  || replace_conf_str(browsefile, &(conf.browsefile))
	  || replace_conf_str(browsedir, &(conf.browsedir)))
		return -1;
	strip_trailing_slashes(&(conf.restoreprefix));
	strip_trailing_slashes(&(conf.browsedir));

	if(conf.mode==MODE_SERVER)
	{
#ifdef HAVE_WIN32
		logp("Sorry, server mode is not implemented for Windows.\n");
#else
		if(act==ACTION_STATUS || act==ACTION_STATUS_SNAPSHOT)
		{
			// We are running on the server machine, being a client
			// of the burp server, getting status information.
			ret=status_client_ncurses(&conf, act, sclient);
		}
		else
			ret=server(&conf, configfile,
				generate_ca_only);
#endif
	}
	else
	{
		logp("before client\n");
		ret=client(&conf, act, vss_restore, json);
		logp("after client\n");
	}

	if(gotlock) unlink(conf.lockfile);
	free_config(&conf);

	// If there was no forking, logfp ends up getting closed before this
	// and will segfault if we try to do it again.
	if(fp && conf.forking) fclose(fp);
	return ret;
}
