/* pref.c
 *
 * Copyright (c) 1992-2016 by Mike Gleason.
 * All rights reserved.
 * 
 */

#include "syshdrs.h"
#ifdef PRAGMA_HDRSTOP
#	pragma hdrstop
#endif

#ifdef ncftp
#include "progress.h"
#endif

#include "pref.h"
#include "util.h"

/* How many times they've run this program. */
extern int gNumProgramRuns;

/* Their $PAGER. */
char gPager[128];

/* These correspond to the various timeouts from LibNcFTP. */
int gConnTimeout, gXferTimeout, gCtrlTimeout;

/* Active or passive FTP?  (PORT or PASV?)  Or both? */
extern int gDataPortMode, gFwDataPortMode;

/* When the destination file already exists, resume transfer or ask user? */
int gAutoResume;

/* "Save a bookmark to this site before closing?" */
int gConfirmClose;

/* Should we update the bookmark for the user? */
int gAutoSaveChangesToExistingBookmarks;

/* "Save your password with the bookmark?" */
int gSavePasswords;

int gMaySetXtermTitle;

/* Number of seconds between connection attempts. */
int gRedialDelay;

/* Some messages we only want to bug the user about once, ever. */
char gOneTimeMessagesSeen[256];

/* Tune the size of the socket buffer using SO_RCVBUF or SO_SNDBUF? */
int gSOBufsize;

/* Size of the user log before we trim it.  0 means do not log at all. */
int gMaxLogSize;

/* Use ASCII mode automatically for files with these extensions. */
char gAutoAscii[512];

#ifdef ncftp
/* Which meter to use. */
FTPProgressMeterProc gProgressMeter;
#endif

/* Allow us to plug our other products? */
int gDoNotDisplayAds;

/* Do we need to save the prefs, or can we skip it? */
int gPrefsDirty = 0;

extern FTPLibraryInfo gLib;
extern FTPConnectionInfo gConn;
extern char gOurDirectoryPath[];

PrefOpt gPrefOpts[] = {
	{ "anonopen",				PREFOBSELETE },
	{ "anonpass", 				SetAnonPass, kPrefOptObselete },
	{ "anon-password",			SetAnonPass, 1 },
	{ "auto-ascii",				SetAutoAscii, 1 },
	{ "auto-resume",			SetAutoResume, 1 },
	{ "autosave-bookmark-changes",		SetAutoSaveChangesToExistingBookmarks, 1 },
	{ "blank-lines",			PREFOBSELETE },
	{ "confirm-close",			SetConfirmClose, 1 },
	{ "connect-timeout",			SetConnTimeout, 1 },
	{ "control-timeout",			SetCtrlTimeout, 1 },
	{ "logsize",				SetLogSize, 1 },
	{ "maxbookmarks",			PREFOBSELETE },
	{ "one-time-messages-seen",		SetOneTimeMessages, 0 },
	{ "pager",				SetPager, 1 },
	{ "passive",				SetPassive, 1 },
	{ "progress-meter",			SetProgressMeter, 1 },
	{ "redial-delay",			SetRedialDelay, 1 },
	{ "remote-msgs", 			PREFOBSELETE },
	{ "restore-lcwd", 			PREFOBSELETE },
	{ "save-passwords",			SetSavePasswords, 1 },
	{ "show-trailing-space",		PREFOBSELETE },
	{ "show-status-in-xterm-titlebar",	SetXtTitle, 1 },
#ifdef SO_RCVBUF
	{ "so-bufsize",				SetSOBufsize, 1 },
#endif
	{ "startup-lcwd", 			PREFOBSELETE },
	{ "startup-msgs", 			PREFOBSELETE },
	{ "timeout",				PREFOBSELETE },
	{ "total-runs", 			PREFOBSELETE },
	{ "total-xfer-hundredths-of-seconds", 	PREFOBSELETE },
	{ "total-xfer-kbytes", 			PREFOBSELETE },
	{ "trace",				PREFOBSELETE },
	{ "utime",				PREFOBSELETE },
	{ "visual",				PREFOBSELETE },
	{ "xfer-timeout",			SetXferTimeout, 1 },
	{ "yes-i-know-about-NcFTPd",		SetNoAds, 1 },
	{ NULL,					(PrefProc) 0, kPrefOptInvisible, },
};

int gNumPrefOpts = ((int)(sizeof(gPrefOpts) / sizeof(PrefOpt)) - 1);



void
SetAnonPass(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%s", gLib.defaultAnonPassword);
	} else {
		(void) STRNCPY(gLib.defaultAnonPassword, val);
	}
}	/* SetAnonPass */



void
SetAutoAscii(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%s", gAutoAscii);
	} else {
		(void) STRNCPY(gAutoAscii, val);
		if ((gAutoAscii[0] == '\0') || (ISTREQ(gAutoAscii, "no")) || (ISTREQ(gAutoAscii, "off")) || (ISTREQ(gAutoAscii, "false"))) {
			gConn.asciiFilenameExtensions = NULL;
		} else {
			gConn.asciiFilenameExtensions = gAutoAscii;
		}
	}
}	/* SetAutoAscii */



void
SetAutoResume(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%s", YESNO(gAutoResume));
	} else {
		gAutoResume = StrToBool(val);
	}
}	/* SetAutoResume */



void
SetAutoSaveChangesToExistingBookmarks(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%s", YESNO(gAutoSaveChangesToExistingBookmarks));
	} else {
		gAutoSaveChangesToExistingBookmarks = StrToBool(val);
	}
}	/* SetAutoSaveChangesToExistingBookmarks */



void
SetConfirmClose(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%s", YESNO(gConfirmClose));
	} else {
		gConfirmClose = StrToBool(val);
	}
}	/* SetConfirmClose */



void
SetConnTimeout(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%d", gConnTimeout);
	} else {
		gConn.connTimeout = (unsigned int) atoi(val);
        gConnTimeout = atoi(val);
	}
}	/* SetConnTimeout */



void
SetCtrlTimeout(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%d", gCtrlTimeout);
	} else {
        gConn.ctrlTimeout = (unsigned int) atoi(val);
        gCtrlTimeout = atoi(val);
	}
}	/* SetCtrlTimeout */



void
SetLogSize(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%d", gMaxLogSize);
	} else {
		gMaxLogSize = atoi(val);
	}
}	/* SetLogSize */



void
SetNoAds(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%s", YESNO(gDoNotDisplayAds));
	} else {
		gDoNotDisplayAds = StrToBool(val);
	}
}	/* SetNoAds */



void
SetOneTimeMessages(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%s", gOneTimeMessagesSeen);
	} else {
		(void) STRNCPY(gOneTimeMessagesSeen, val);
	}
}	/* SetOneTimeMessages */



void
SetPager(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%s", gPager);
	} else {
		(void) STRNCPY(gPager, val);
	}
}	/* SetPager */



void
SetPassive(const char *const val, FILE *const fp)
{
	int m;

	if (fp != NULL) {
		m = (gFwDataPortMode >= 0) ? gFwDataPortMode : gDataPortMode;
		if (m == kSendPortMode) {
			(void) fprintf(fp, "%s", "off");
		} else if (m == kPassiveMode) {
			(void) fprintf(fp, "%s", "on");
		} else {
			(void) fprintf(fp, "%s", "optional");
		}
	} else {
		if (gFwDataPortMode >= 0) {
			gDataPortMode = gFwDataPortMode;
			return;
		}
		if (ISTRNEQ(val, "opt", 3))
			gDataPortMode = kFallBackToSendPortMode;
		else if (ISTREQ(val, "on"))
			gDataPortMode = kPassiveMode;
		else if (isdigit((int) val[0]))
			gDataPortMode = atoi(val);
		else
			gDataPortMode = kSendPortMode;
		gConn.dataPortMode = gDataPortMode;
	}
}	/* SetPassive */



#ifdef ncftp
void
SetProgressMeter(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		if (gProgressMeter == PrStatBar) {
			(void) fprintf(fp, "%s", "2 (statbar)");
		} else if (gProgressMeter == PrPhilBar) {
			(void) fprintf(fp, "%s", "1 (philbar)");
		} else {
			(void) fprintf(fp, "%s", "0 (simple)");
		}
	} else {
		if ((val[0] == '0') || (ISTRNEQ(val, "simple", 6)))
			gProgressMeter = PrSizeAndRateMeter;
		else if ((val[0] == '1') || (ISTRNEQ(val, "phil", 4)))
			gProgressMeter = PrPhilBar;
		else
			gProgressMeter = PrStatBar;
		gConn.progress = gProgressMeter;
	}
}	/* SetProgressMeter */
#else
void
SetProgressMeter(const char *const UNUSED(val), FILE *const UNUSED(fp))
{
	LIBNCFTP_USE_VAR(val);
	LIBNCFTP_USE_VAR(fp);
}	/* SetProgressMeter */
#endif



void
SetRedialDelay(const char *const val, FILE *const fp)
{
	int i;

	if (fp != NULL) {
		(void) fprintf(fp, "%d", gRedialDelay);
	} else {
		i = atoi(val);
		if (i < 10)
			i = 10;
		gRedialDelay = atoi(val);
	}
}	/* SetRedialDelay */



void
SetSavePasswords(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		if (gSavePasswords < 0)
			(void) fprintf(fp, "%s", "ask");
		else
			(void) fprintf(fp, "%s", YESNO(gSavePasswords));
	} else {
		if (ISTREQ(val, "ask"))
			gSavePasswords = -1;
		else
			gSavePasswords = StrToBool(val);
	}
}	/* SetSavePasswords */



void
SetSOBufsize(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%d", gSOBufsize);
		if (gSOBufsize <= 0)
			(void) fprintf(fp, "%s", " (use system default)");
	} else {
		gConn.dataSocketRBufSize = gConn.dataSocketSBufSize = (size_t) atoi(val);
        gSOBufsize = atoi(val);
	}
}	/* SetSOBufsize */




void
SetXferTimeout(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%d", gXferTimeout);
	} else {
		gConn.xferTimeout = (unsigned int) atoi(val);
        gXferTimeout = atoi(val);
	}
}	/* SetXferTimeout */



void
SetXtTitle(const char *const val, FILE *const fp)
{
	if (fp != NULL) {
		(void) fprintf(fp, "%s", YESNO(gMaySetXtermTitle));
	} else {
		gMaySetXtermTitle = StrToBool(val);
	}
}	/* SetXtTitle */




static void
Show1(int t)
{
	PrefOpt *p = &gPrefOpts[t];

	(void) printf("%-30s ", p->varname);
	if (p->proc != (PrefProc) 0)
		(*p->proc)(NULL, stdout);
	(void) printf("\n");
}	/* Show1 */




/* Modify or display the program's configuration. */
void
Set(const char *const tok1, const char *const tok2)
{
	int t;

	if ((tok1 == NULL) || (ISTREQ(tok1, "all"))) {
		/* Show all. */
		for (t=0; t<gNumPrefOpts; t++) {
			if (gPrefOpts[t].visible == kPrefOptVisible)
				Show1(t);
		}
	} else if (tok2 == NULL) {
		/* Show one. */
		for (t=0; t<gNumPrefOpts; t++) {
			if (ISTREQ(tok1, gPrefOpts[t].varname)) {
				if (gPrefOpts[t].visible == kPrefOptObselete) {
					(void) printf("The \"%s\" option is obselete or not implemented.\n", tok1);
				} else {
					Show1(t);
				}
				break;
			}
		}
		if (t >= gNumPrefOpts) {
			(void) printf("Unknown option \"%s\" -- try \"show all\" to list available options.\n", tok1);
		}
	} else {
		/* Set one. */
		for (t=0; t<gNumPrefOpts; t++) {
			if (ISTREQ(tok1, gPrefOpts[t].varname)) {
				if (gPrefOpts[t].visible == kPrefOptObselete) {
					(void) printf("The \"%s\" option is obselete or not implemented.\n", tok1);
				} else if (gPrefOpts[t].proc != (PrefProc) 0) {
					(*gPrefOpts[t].proc)(tok2, NULL);
					gPrefsDirty++;
				}
				break;
			}
		}
		if (t >= gNumPrefOpts) {
			(void) printf("Unknown option \"%s\" -- try \"show all\" to list available options.\n", tok1);
		}
	}
}	/* Set */



int
HasSeenOneTimeMessage(const char *const msg)
{
	char buf[256];
	char *a, *b;
	
	memcpy(buf, gOneTimeMessagesSeen, sizeof(buf));
	for (a = buf; (b = strtok(a, ",\n")) != NULL; a = NULL) {
		if (strcmp(msg, b) == 0)
			return (1);
	}
	return (0);
}	/* HasSeenOneTimeMessage */




void
SetSeenOneTimeMessage(const char *const msg)
{
	gPrefsDirty++;
	if (gOneTimeMessagesSeen[0] == '\0')
		STRNCPY(gOneTimeMessagesSeen, msg);
	else {
		STRNCAT(gOneTimeMessagesSeen, ",");
		STRNCAT(gOneTimeMessagesSeen, msg);
	}	
}	/* SetSeenOneTimeMessage */



int
OneTimeMessage(const char *const msg)
{
	if (HasSeenOneTimeMessage(msg))
		return (0);
	SetSeenOneTimeMessage(msg);
	return (1);
}	/* OneTimeMessage */




void
ProcessPrefsFile(FILE *const fp)
{
	char line[1024];
	char *tok1, *tok2;
	int t;

	line[sizeof(line) - 1] = '\0';
	while (fgets(line, sizeof(line) - 1, fp) != NULL) {
		tok1 = strtok(line, " =\t\r\n");
		if ((tok1 == NULL) || (tok1[0] == '#'))
			continue;
		tok2 = strtok(NULL, "\r\n");
		if (tok2 == NULL)
			continue;

		for (t=0; t<gNumPrefOpts; t++) {
			if (ISTREQ(tok1, gPrefOpts[t].varname)) {
				if (gPrefOpts[t].visible == kPrefOptObselete) {
					/* Probably converting an
					 * old 2.4.2 file.
					 */
					gPrefsDirty++;
				} else if (gPrefOpts[t].proc != (PrefProc) 0) {
					(*gPrefOpts[t].proc)(tok2, NULL);
				}
			}
		}
	}
}	/* ProcessPrefsFile */




/* Read the saved configuration settings from a preferences file. */
void
LoadPrefs(void)
{
	FILE *fp;
	char pathName[256];

	/* As with the firewall preference file, there can be
	 * site-wide preferences and user-specific preferences.
	 * The user pref file is of course kept in the user's
	 * NcFTP home directory.
	 *
	 * The way we do this is we first look for a global
	 * preferences file.  We then process the user's pref
	 * file, which could override the global prefs.  Finally,
	 * we open a "global fixed" prefs file which then
	 * overrides anything the user may have done.
	 */

	fp = fopen(kGlobalPrefFileName, FOPEN_READ_TEXT);
	if (fp != NULL) {
		/* Opened the global (but user-overridable) prefs file. */
		ProcessPrefsFile(fp);
		(void) fclose(fp);
	}

	if (gOurDirectoryPath[0] != '\0') {
		(void) OurDirectoryPath(pathName, sizeof(pathName), kPrefFileName);

		fp = fopen(pathName, FOPEN_READ_TEXT);
		if (fp == NULL) {
			/* Try loading the version 2 prefs.
			 * There will be options we no longer recognize, but
			 * we'd like to import the prefs when possible.
			 */
			gPrefsDirty++;
			(void) OurDirectoryPath(pathName, sizeof(pathName), kPrefFileNameV2);
			fp = fopen(pathName, FOPEN_READ_TEXT);
		}

		if (fp == NULL) {
			/* Write a new one when we're done. */
			gPrefsDirty++;
		} else {
			/* Opened the preferences file. */
			ProcessPrefsFile(fp);
			(void) fclose(fp);
		}
	}

	fp = fopen(kGlobalFixedPrefFileName, FOPEN_READ_TEXT);
	if (fp != NULL) {
		/* Opened the global (and not overridable) prefs file. */
		ProcessPrefsFile(fp);
		(void) fclose(fp);
	}
}	/* LoadPrefs */




/* Initialize the configuration settings, in case the user does not set them. */
void
InitPrefs(void)
{
	char *tok1;

	/* Set default values. */
	gPager[0] = '\0';
	memset(gOneTimeMessagesSeen, 0, sizeof(gOneTimeMessagesSeen));
	gXferTimeout = 3600;
	gConnTimeout = 20;
	gCtrlTimeout = 135;
	gDataPortMode = kFallBackToSendPortMode;
	gConn.dataPortMode = gDataPortMode;
	gAutoResume = 1;
	gSOBufsize = 0;
	gMaxLogSize = 10240;
	gConfirmClose = 0;
	gAutoSaveChangesToExistingBookmarks = 0;
	gRedialDelay = kDefaultRedialDelay;
	STRNCPY(gAutoAscii, "|.txt|.asc|.html|.htm|.css|.xml|.ini|.pl|.hqx|.cfg|.c|.h|.cpp|.hpp|.bat|.m3u|.pls|");

	/* PLEASE do not change the default from 0, and please
	 * don't hack out the portion in main.c which displays
	 * a plug every 7th time you run the program.  This is
	 * not much to ask for all the work I've put into this
	 * since 1991.
	 */
	gDoNotDisplayAds = 0;

#if ( (defined(WIN32) || defined(_WINDOWS)) && !defined(__CYGWIN__) )\
	&& defined(_CONSOLE)
	gMaySetXtermTitle = 1;
#else
	gMaySetXtermTitle = 0;
#endif

	gSavePasswords = -1;
#ifdef ncftp
	gProgressMeter = PrStatBar;
#endif

	tok1 = getenv("PAGER");
	if ((tok1 != NULL) && (tok1[0] != '\0')) {
#ifdef HAVE_STRSTR
		/* I prefer "less", but it doesn't work well here
		 * because it clears the screen after it finishes,
		 * and the default at EOF is to stay in less
		 * instead of exiting.
		 */
		if (strstr(gPager, "less") != NULL)
			(void) STRNCPY(gPager, "more");
		else
			(void) STRNCPY(gPager, tok1);
#else
		(void) STRNCPY(gPager, tok1);
#endif
	} else {
		(void) STRNCPY(gPager, "more");
	}
}	/* InitPrefs */




/* After reading the preferences, do some additional initialization. */
void
PostInitPrefs(void)
{
	if (gLib.defaultAnonPassword[0] == '\0') {
		FTPInitializeAnonPassword(&gLib);
		gPrefsDirty++;
	}
	if (gFwDataPortMode >= 0)
		gConn.dataPortMode = gFwDataPortMode;
}	/* PostInitPrefs */




/* Write the configuration settings to a preferences file. */
void
SavePrefs(void)
{
	char pathName[256];
	char pathName2[256];
	char tName[32];
	int t;
	FILE *fp;

	if (gPrefsDirty == 0)
		return;		/* Don't need to save -- no changes made. */

	(void) OurDirectoryPath(pathName, sizeof(pathName), kPrefFileName);

	(void) sprintf(tName, "tpref%06u.txt", (unsigned int) getpid());
	(void) OurDirectoryPath(pathName2, sizeof(pathName2), tName);

	fp = fopen(pathName2, FOPEN_WRITE_TEXT);
	if (fp == NULL) {
		perror("could not save preferences file");
	} else {
		(void) fprintf(fp, "%s", "# NcFTP 3 preferences file\n# This file is loaded and overwritten each time NcFTP is run.\n#\n");
		for (t=0; t<gNumPrefOpts; t++) {
			if (gPrefOpts[t].visible != kPrefOptObselete) {
				(void) fprintf(fp, "%s=", gPrefOpts[t].varname);
				(*gPrefOpts[t].proc)(NULL, fp);
				(void) fprintf(fp, "\n");
			}
		}
		(void) fclose(fp);
		(void) unlink(pathName);
		if (rename(pathName2, pathName) < 0) {
			perror("could not finish saving preferences file");
			(void) unlink(pathName2);
		};
	}
}	/* SavePrefs */



/* This maintains the little counter file that is used by version 3.0
 * to do things based on how many times the program was run.
 */
void
CheckForNewV3User(void)
{
	FILE *fp;
	struct stat st;
	char pathName[256];
	char line[256];

	gNumProgramRuns = 0;

	/* Don't create in root directory. */
	if (gOurDirectoryPath[0] != '\0') {
		(void) OurDirectoryPath(pathName, sizeof(pathName), kFirstFileName);

		if ((stat(pathName, &st) < 0) && (errno == ENOENT)) {
			gNumProgramRuns = 1;
			gPrefsDirty++;

			/* Create a blank one. */
			fp = fopen(pathName, FOPEN_WRITE_TEXT);
			if (fp == NULL)
				return;
			(void) fprintf(fp, "# NcFTP uses this file to mark that you have run it before, and that you do not\n# need any special first-time instructions or setup.\n#\nruns=%d\n", gNumProgramRuns);
			(void) fclose(fp);
		} else {
			fp = fopen(pathName, FOPEN_READ_TEXT);
			if (fp != NULL) {
				while (fgets(line, sizeof(line) - 1, fp) != NULL) {
					if (strncmp(line, "runs=", 5) == 0) {
						(void) sscanf(line + 5, "%d",
							&gNumProgramRuns);
						break;
					}
				}
				(void) fclose(fp);
			}

			/* Increment the count of program runs. */
			gNumProgramRuns++;
			if (gNumProgramRuns == 1)
				gPrefsDirty++;

			/* Race condition between other ncftp processes.
			 * This isn't a big deal because this counter isn't
			 * critical.
			 */

			fp = fopen(pathName, FOPEN_WRITE_TEXT);
			if (fp != NULL) {
				(void) fprintf(fp, "# NcFTP uses this file to mark that you have run it before, and that you do not\n# need any special first-time instructions or setup.\n#\nruns=%d\n", gNumProgramRuns);
				(void) fclose(fp);
			}
		}
	}
}	/* CheckForNewV3User */
