/* Main.c */

#define _main_c_ 1

/* This is copyrighted software.  Read the entire ``COPYRIGHT.h'' file
 * before compiling or using!
 */
#include "COPYRIGHT.h"

#include "Sys.h"

#if (LOCK_METHOD == 2)
#	ifdef HAVE_SYS_FILE_H
#		include <sys/file.h>
#	endif
#endif

#ifdef SYSLOG
#	include <syslog.h>
#endif

#include <pwd.h>
#include <errno.h>
#include <signal.h>
#include <setjmp.h>

#include "Util.h"
#include "Main.h"
#include "Cmds.h"
#include "Open.h"
#include "Cmdline.h"
#include "DateSize.h"
#include "Recent.h"
#include "Prefs.h"
#include "FTP.h"
#include "Getopt.h"
#include "Xfer.h"
#include "Tips.h"
#include "Version.h"

/* We need to know our fully qualified hostname.  That way we can give
 * a complete email address as a password for anonymous logins.
 */
string gOurHostName;

/* The user's full email address, which we use for the password on
 * anonymous logins.
 */
string gEmailAddress;

/* This is what we'll actually use for the anonymous password.
 * We keep an unmodified copy of gEmailAddress, and let the user
 * change the variable below if they want to.
 */
string gAnonPassword;

/* Since we create a bunch of files, instead of making them all
 * .dot files in the home directory, we have our own .dot directory
 * and keep all our stuff in there.
 */
string gOurDirectoryPath;

/* We keep some basic information about the user.  Most of the structure
 * is just a copy of the user's password file entry.  Instead of keeping
 * just that pointer from getpwnam(), we make our own copy so we can
 * use getpwnam() again later.
 */
UserInfo gUserInfo;

/* Boolean indicating whether input is coming from a live user or
 * a file.
 */
int gIsFromTTY;

/* Boolean indicating whether output is going to the screen or to
 * a file.
 */
int gIsToTTY;

/* Boolean indicating whether input is coming from a script file.
 * This is really just a copy of gIsFromTTY.
 */
int gDoingScript;

/* A FILE pointer to the user's usage log, or NULL if we're not
 * logging anything.
 */
FILE *gLogFile = NULL;

/* We can optionally log all the debugging information to a separate
 * log file.
 */
FILE *gTraceLogFile = NULL;

/* Full pathname of the user's usage log file, in our .dot directory. */
longstring gLogFileName;

/* Maximum size we will let the log grow to before making it smaller.
 * If this is zero, there is no limit.
 */
int gMaxLogSize = 10240;

/* Boolean indicating whether the user wanted to keep a usage log. */
int gLogging = USERLOG;

/* Full pathname of the user's pager.  Maybe be just "more" if the user
 * doesn't have a PAGER environment variable, or didn't set it explicitly.
 */
longstring gPager;

int gVisualMode;
int gDefaultVisualMode = VISUAL;

/* If another ncftp process is running, we don't want to overwrite the
 * the files in our directory.  We will only read them to prevent the
 * user losing changes made in the first process.
 */
int gOtherSessionRunning = 0;

/* We track the number of times the user has run the program.  We may
 * use that information for something else later.
 */
int gTotalRuns = 0;

/* Types of startup messages user wants us to print when we first run. */
int gStartupMsgs = (kStartupMsg | kTips);

string gVersion;

/* If this user variable is set, we will change directory to the last
 * local directory the user was in when the user exited.
 */
int gRememberLCWD = 0;

/* If this user variable is set, we will always chdir to this value each
 * time we run the program.
 */
longstring gDownloadDir = "";

jmp_buf gMainJmp;

/* We save previously entered commands between sessions, so the user can
 * use the history.
 */
LineList gCmdHistory;

/* Name of the file we use to tell if another ncftp process is running. */
longstring gLockFileName = "";

extern int gStdout, gRealStdout;
extern char *getlogin(void);
extern longstring gLocalCWD;
extern time_t gMailBoxTime;
extern int gWinInit, gOptErr, gTrace, gDebug;
extern char *gSprintfBuf;
extern jmp_buf gCmdLoopJmp;
extern LineList gRedir;



/* This looks up the user's password entry, trying to look by the username.
 * We have a couple of extra hacks in place to increase the probability
 * that we can get the username.
 */
static
struct passwd *GetPwByName(void)
{
	char *cp;
	struct passwd *pw;
	
	cp = getlogin();
	if (cp == NULL) {
		cp = (char *) getenv("LOGNAME");
		if (cp == NULL)
			cp = (char *) getenv("USER");
	}
	pw = NULL;
	if (cp != NULL)
		pw = getpwnam(cp);
	return (pw);
}	/* GetPwByName */




void GetUserInfo(void)
{
	register char			*cp;
	struct passwd			*pw;
	string					str;
	struct stat				st;

	pw = NULL;
	errno = 0;
#ifdef USE_GETPWUID
	/* Try to use getpwuid(), but if we have to, fall back to getpwnam(). */
	if ((pw = getpwuid(getuid())) == NULL)
		pw = GetPwByName();	/* Oh well, try getpwnam() then. */
#else
	/* Try to use getpwnam(), but if we have to, fall back to getpwuid(). */
	if ((pw = GetPwByName()) == NULL)
		pw = getpwuid(getuid());	/* Try getpwnam() then. */
#endif
	if (pw != NULL) {
		gUserInfo.uid = pw->pw_uid;
		(void) STRNCPY(gUserInfo.userName, pw->pw_name);
		gUserInfo.shell = StrDup(pw->pw_shell);
		if ((cp = (char *) getenv("HOME")) != NULL)
			gUserInfo.home = StrDup(cp);
		else
			gUserInfo.home = StrDup(pw->pw_dir);

		cp = (char *) getenv("MAIL");
		if (cp == NULL)
			cp = (char *) getenv("mail");
		if (cp != NULL) {
			/* We do have a Mail environment variable. */
			(void) STRNCPY(str, cp);
			cp = str;
	
			/* Mail variable may be like MAIL=(28 /usr/mail/me /usr/mail/you),
			 * so try to find the first mail path.
			 */
			while ((*cp != '/') && (*cp != 0))
				cp++;
			gUserInfo.mail = StrDup(cp);
			if ((cp = strchr(gUserInfo.mail, ' ')) != NULL)
				*cp = '\0';
		} else {
			/* Guess between /usr/mail and /usr/spool/mail as
			 * possible directories.  We'll just choose /usr/spool/mail
			 * if we have to.
			 */
			(void) sprintf(str, "/usr/mail/%s", gUserInfo.userName);
			if (stat(str, &st) < 0)
				(void) sprintf(str, "/usr/spool/mail/%s", gUserInfo.userName);
			gUserInfo.mail = StrDup(str);
		}
	} else {
		/* Couldn't get information about this user.  Since this isn't
		 * the end of the world as far as I'm concerned, make up
		 * some stuff that might work good enough.
		 */
		Error(kDoPerror, "Could not get your passwd entry!");
		(void) STRNCPY(gUserInfo.userName, "nobody");
		gUserInfo.shell = StrDup("/bin/sh");
		if ((cp = (char *) getenv("HOME")) == NULL)
			cp = ".";
		gUserInfo.home = StrDup(cp);
		gUserInfo.uid = 999;
		gUserInfo.mail = NULL;
	}

	DebugMsg("GetOurHostName returned %d, %s.\n",	
		GetOurHostName(gOurHostName, sizeof(gOurHostName)),
		gOurHostName
	);
	STRNCPY(gEmailAddress, gUserInfo.userName);
	STRNCAT(gEmailAddress, "@");
	STRNCAT(gEmailAddress, gOurHostName);
	STRNCPY(gAnonPassword, gEmailAddress);
}	/* GetUserInfo */




/* Setup the malloc library, if we're using one. */
static void InitMalloc(void)
{
#ifdef DEBUG
#	if (LIBMALLOC == FAST_MALLOC)
	FILE *malloclog;

	mallopt(M_DEBUG, 1);
	mallopt(M_LOG, 1);	/* turn on/off malloc/realloc/free logging */
	/* set the file to use for logging */
	if ((malloclog = fopen("malloc.log", "w")) != NULL)
		mallopt(M_LOGFILE, fileno(malloclog)); 
#	endif
#endif

#if (LIBMALLOC == DEBUG_MALLOC)
	/* Thanks to Conor Cahill for making something like this possible! */
	union dbmalloptarg  m;

#	if 1
	m.i = 1;
	dbmallopt(MALLOC_CKCHAIN, &m);
#	endif

#	if 1
	m.i = M_HANDLE_IGNORE;
	dbmallopt(MALLOC_WARN, &m);
#	endif

#	if 0	/* If 0, stderr. */
	m.str = "malloc.log";
	dbmallopt(MALLOC_ERRFILE, &m);
#	endif
#endif
}	/* InitMalloc */




void OpenTraceLog(void)
{
	string traceLogPath;
	string traceLog2Path;
	string traceLogTmpPath;
	string line;
	FILE *f1, *f2, *f3;
	int i, lim;
	time_t now;

	if (gOtherSessionRunning != 0)
		return;

	if (gTraceLogFile != NULL)
		return;	/* Already open. */

	OurDirectoryPath(traceLogPath, sizeof(traceLogPath), kTraceLogName);	
	if (access(traceLogPath, F_OK) == 0) {
		/* Need to prepend last session's log to the master debug log. */
		OurDirectoryPath(traceLog2Path, sizeof(traceLog2Path), kTraceLog2Name);
		OurDirectoryPath(traceLogTmpPath, sizeof(traceLogTmpPath), kTraceLogTmpName);

		f1 = fopen(traceLogTmpPath, "w");
		if (f1 == NULL) {
			Error(kDoPerror, "Can't open %s for writing.\n", traceLogTmpPath);
			return;
		}
		f2 = fopen(traceLogPath, "r");
		if (f2 == NULL) {
			Error(kDoPerror, "Can't open %s for reading.\n", traceLogPath);
			return;
		}
		
		lim = kMaxTraceLogLines - 1;
		for (i=0; i<lim; i++) {
			if (fgets(line, ((int) sizeof(line)) - 1, f2) == NULL)
				break;
			fputs(line, f1);
		}
		if (i + 2 < lim) {
			fputs("\n\n", f1);
			i += 2;
		}
		(void) fclose(f2);
		f3 = fopen(traceLog2Path, "r");
		if (f3 != NULL) {
			for (; i<lim; i++) {
				if (fgets(line, ((int) sizeof(line)) - 1, f3) == NULL)
					break;
				fputs(line, f1);
			}
			(void) fclose(f3);			
		}

		if (i >= lim)
			fputs("...Remaining lines omitted...\n", f1);

		(void) fclose(f1);
		(void) UNLINK(traceLogPath);
		(void) UNLINK(traceLog2Path);
		(void) rename(traceLogTmpPath, traceLog2Path);
	}

	gTraceLogFile = fopen(traceLogPath, "w");
	if (gTraceLogFile == NULL) {
		Error(kDoPerror, "Can't open %s for writing.\n", traceLogPath);
	} else {
		time(&now);
		fprintf(
			gTraceLogFile,
			"SESSION STARTED at %s%s\n\n",
			ctime(&now),
			"-----------------------------------------------"
		);
	}
}	/* OpenTraceLog */




void OpenLogs(void)
{
	/* First try open the user's private log, unless the user
	 * has this option turned off.
	 */
	OurDirectoryPath(gLogFileName, sizeof(gLogFileName), kLogName);
	gLogFile = NULL;
	if (gOtherSessionRunning == 0) {
		if ((gLogging) && (gMaxLogSize > 0))
			gLogFile = fopen(gLogFileName, "a");

		if (gTrace == kTracingOn)
			OpenTraceLog();
	}

	/* Open a port to the system log, if you're hellbent on knowing
	 * everything users do with the program.
	 */
#ifdef SYSLOG
#	ifdef LOG_LOCAL3
	openlog ("NcFTP", LOG_PID, LOG_LOCAL3);
#	else
	openlog ("NcFTP", LOG_PID);
#	endif
#endif				/* SYSLOG */
}	/* OpenLogs */




/* Create, if necessary, a directory in the user's home directory to
 * put our incredibly important stuff in.
 */
void InitOurDirectory(void)
{
	struct stat st;
	char *cp;

	cp = getenv("NCFTPDIR");
	if (cp != NULL)
		STRNCPY(gOurDirectoryPath, cp);
	else (void) Path(
		gOurDirectoryPath,
		sizeof(gOurDirectoryPath),
		gUserInfo.home,
		kOurDirectoryName
	);

	if (stat(gOurDirectoryPath, &st) < 0) {
		(void) mkdir(gOurDirectoryPath, 00755);
	}
}	/* InitOurDirectory */



/* There would be problems if we had multiple ncftp's going.  They would
 * each try to update the files in ~/.ncftp.  Whichever program exited
 * last would have its stuff written, and the others which may have made
 * changes, would lose the changes they made.
 *
 * This checks to see if another ncftp is running.  To do that we try
 * to lock a special file.  If we could make the lock, then we are the first
 * ncftp running and our changes will be written.  Other ncftps that may
 * start will not be able to save their changes.
 *
 * No matter what locking method in use, whenever we Exit() we try to
 * remove the lock file if we had the lock.  For the first two methods,
 * exiting releases the lock automatically.
 */
void CheckForOtherSessions(void)
{
	int fd;
	char pidbuf[64];
	time_t now;

#if (LOCK_METHOD == 1)
	struct flock l;
	int err;

	l.l_type = F_WRLCK;
	l.l_start = 0;
	l.l_whence = SEEK_SET;
	l.l_len = 1;

	OurDirectoryPath(gLockFileName, sizeof(gLockFileName), kLockFileName);
	if ((fd = open(gLockFileName, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR)) < 0) {
		Error(kDoPerror, "Could not open lock file %s.\n", gLockFileName);
		sleep(5);

		/* Try to save data anyway. */
		gOtherSessionRunning = 0;
	} else {
		err = fcntl(fd, F_SETLK, &l);
		if (err == 0) {
			/* We now have a lock set on the first byte. */
			gOtherSessionRunning = 0;
			time(&now);
			sprintf(pidbuf, "%5d %s", (int) getpid(), ctime(&now));
			write(fd, pidbuf, strlen(pidbuf) + 1);
		} else {
			/* Could not lock;  maybe another ncftp process has
			 * already locked it.
			 */
			gOtherSessionRunning = 1;
		}
	}
#endif	/* (LOCK_METHOD == 1) */

#if (LOCK_METHOD == 2)	/* BSD's flock */
	int err;

	OurDirectoryPath(gLockFileName, sizeof(gLockFileName), kLockFileName);
	if ((fd = open(gLockFileName, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR)) < 0) {
		Error(kDoPerror, "Could not open lock file %s.\n", gLockFileName);
		sleep(5);

		/* Try to save data anyway. */
		gOtherSessionRunning = 0;
	} else {
		err = flock(fd, LOCK_EX | LOCK_NB);
		if (err == 0) {
			/* We now have a lock set on the whole file. */
			gOtherSessionRunning = 0;
			time(&now);
			sprintf(pidbuf, "%5d %s", (int) getpid(), ctime(&now));
			write(fd, pidbuf, strlen(pidbuf) + 1);
		} else {
			/* Could not lock;  maybe another ncftp process has
			 * already locked it.
			 */
			gOtherSessionRunning = 1;
		}
	}
#endif	/* (LOCK_METHOD == 2) */

#if (LOCK_METHOD == 3)	/* Cheezy lock */
	/* For this locking mechanism, we consider it locked if another
	 * process had already created the lockfile.  Unfortunately we
	 * aren't guaranteed to get the lock file removed if the program
	 * crashes somewhere without explicitly removing the lockfile.
	 */
	int fd;

	OurDirectoryPath(gLockFileName, sizeof(gLockFileName), kLockFileName);
	if ((fd = open(gLockFileName, O_CREAT | O_EXCL | O_WRONLY, S_IWUSR)) < 0) {
		gOtherSessionRunning = 1;
	} else {
		gOtherSessionRunning = 0;
		/* Unlike the other two methods, we don't need to keep the file
		 * open.
		 */
		close(fd);
	}
#endif	/* (LOCK_METHOD == 3) */
}	/* CheckForOtherSessions */




void Init(void)
{
	char *cp;
	
	gRealStdout = gStdout = 1;
	InitMalloc();
	InitXferBuffer();
	if ((gSprintfBuf = malloc(SZ(4096))) == NULL)
		OutOfMemory();

#if (kAlpha > 0)
		sprintf(gVersion, "%s Alpha %d (%s)",
			kVersion,
			kAlpha,
			kVersionDate
		);
#else
#	if (kBeta > 0)
		sprintf(gVersion, "%s Beta %d (%s)",
			kVersion,
			kBeta,
			kVersionDate
		);
#	else
		sprintf(gVersion, "%s (%s)",
			kVersion,
			kVersionDate
		);
#	endif
#endif

	srand((unsigned int) time(NULL));

	GetUserInfo();
	InitDefaultFTPPort();
	if ((cp = (char *) getenv("PAGER")) != NULL)
		STRNCPY(gPager, cp);
	else {
#ifdef MORE
		STRNCPY(gPager, MORE);
#else
		gPager[0] = '\0';
#endif
	}
	gIsFromTTY = gDoingScript = isatty(0);
	gIsToTTY = isatty(1);
	(void) UserLoggedIn();	/* Init parent-death detection. */

	/* Init the mailbox checking code. */
	(void) time(&gMailBoxTime);

	/* Clear the redir buffer. */
	InitLineList(&gRedir);

	(void) GetCWD(gLocalCWD, sizeof(gLocalCWD));

	/* Compute our own private directory's name, and create it
	 * if we have to.  Stuff that depends on this will need
	 * to follow (duh).
	 */
	(void) InitOurDirectory();

	(void) CheckForOtherSessions();
	ReadPrefs();

	OpenLogs();
	ReadRemoteInfoFile();
	ReadMacroFile();
	InitCommandAndMacroNameList();
	
	++gTotalRuns;
}	/* Init */




void CloseTraceLog(void)
{
	time_t now;

	if (gTraceLogFile != NULL) {
		time(&now);
		fprintf(
			gTraceLogFile,
			"\nSESSION ENDED at %s",
			ctime(&now)
		);
		(void) fclose(gTraceLogFile);
		gTraceLogFile = NULL;
	}
}	/* CloseTraceLog */




void CloseLogs(void)
{
	FILE *new, *old;
	struct stat st;
	long fat;
	string str;
	longstring tmpLog;

#ifdef SYSLOG
	/* Close system log first. */
	closelog();
#endif

	/* Close the debugging log next. */
	CloseTraceLog();

	/* The rest is for the user's log. */
	if (!gLogging)
		return;

	CloseFile(&gLogFile);
	
	/* If the user wants to, s/he can specify the maximum size of the log file,
	 * so it doesn't waste too much disk space.  If the log is too fat, trim the
	 * older lines (at the top) until we're under the limit.
	 */
	if ((gMaxLogSize < 0) || (stat(gLogFileName, &st) < 0) ||
		((old = fopen(gLogFileName, "r")) == NULL))
		return;						   /* Never trim, or no log. */

	if (st.st_size < (size_t)gMaxLogSize)
		return;						   /* Log size not over limit yet. */

	/* Want to make it so we're about 30% below capacity.
	 * That way we won't trim the log each time we run the program.
	 */
	fat = st.st_size - gMaxLogSize + (long) (0.30 * gMaxLogSize);
	DebugMsg("%s was over %ld limit; trimming at least %ld...\n",
		gLogFileName,
		gMaxLogSize,
		fat
	);
	while (fat > 0L) {
		if (fgets(str, (int) sizeof(str), old) == NULL)
			return;
		fat -= (long) strlen(str);
	}
	/* skip lines until a new site was opened */
	while (1) {
		if (fgets(str, (int) sizeof(str), old) == NULL) {
			(void) fclose(old);
			(void) UNLINK(gLogFileName);
			return;					   /* Nothing left, start anew next time. */
		}
		if (*str != '\t')
			break;
	}

	/* Copy the remaining lines in "old" to "new" */
	OurDirectoryPath(tmpLog, sizeof(tmpLog), kTmpLogName);
	if ((new = fopen(tmpLog, "w")) == NULL) {
		(void) Error(kDoPerror, "Could not open %s.\n", tmpLog);
		return;
	}
	(void) fputs(str, new);
	while (fgets(str, (int) sizeof(str), old) != NULL)
		(void) fputs(str, new);
	(void) fclose(old);
	(void) fclose(new);
	if (UNLINK(gLogFileName) < 0)
		(void) Error(kDoPerror, "Could not delete %s.\n", gLogFileName);
	if (rename(tmpLog, gLogFileName) < 0)
		(void) Error(kDoPerror, "Could not rename %s to %s.\n",
			tmpLog, gLogFileName);
}									   /* CloseLogs */




void SaveHistory(void)
{
	string histFileName;
	FILE *fp;
	LinePtr lp;
	int i;

	if (gWinInit) {
		lp = gCmdHistory.last;
		OurDirectoryPath(histFileName, sizeof(histFileName), kHistoryName);
		fp = fopen(histFileName, "w");
		if ((fp != NULL) && (lp != NULL)) {
			for (i = 1 ; (lp->prev != NULL) && (i < kMaxHistorySaveLines); i++)
				lp = lp->prev;
			for ( ; lp != NULL; lp = lp->next)
				fprintf(fp, "%s\n", lp->line);
			fclose(fp);
		}
	}
}	/* SaveHistory */




void LoadHistory(void)
{
	string histFileName;
	string str;
	FILE *fp;

	InitLineList(&gCmdHistory);
	OurDirectoryPath(histFileName, sizeof(histFileName), kHistoryName);
	fp = fopen(histFileName, "r");
	if (fp != NULL) {
		while (FGets(str, sizeof(str), fp) != NULL)
			AddLine(&gCmdHistory, str);
	}
}	/* LoadHistory */




void StartupMsgs(void)
{
	char curLocalCWD[512];

	if ((gStartupMsgs & kStartupMsg) != 0) {
#if (kAlpha > 0) || (kBeta > 0)
		if (gWinInit == 0)
			PrintF("NcFTP %s, by Mike Gleason, NCEMRSoft.\n", gVersion);
		BoldPrintF(
		"This pre-release is for testing only.  Please do not archive.\n\n");
		BoldPrintF(
		"Check the CHANGELOG file for changes between betas.\n\n");
#else
		if (gWinInit == 0)
			PrintF("NcFTP %s, by Mike Gleason, NCEMRSoft.\n", gVersion);
#endif
	}
	if (gTotalRuns < 2) {
		PrintF("%s%s%s\n", gCopyright1 + 5, gCopyright2, gCopyright3);
	} else if ((gStartupMsgs & kTips) != 0) {
		PrintRandomTip();
	}

	if (gOtherSessionRunning) {
		BoldPrintF("Note: Another session is running. Changes made to the\n");
		BoldPrintF("      prefs, hosts, and trace files will not be saved.\n\n");
	}

	(void) GetCWD(curLocalCWD, sizeof(curLocalCWD));
	if (gDownloadDir[0] != '\0') {
		/* If set, always try to cd there. */
		(void) chdir(gDownloadDir);	/* May not succeed. */
	} else if (gRememberLCWD) {
		/* Try restoring the last local directory we were in.
		 * gLocalCWD may be set when the prefs are read in.
		 */
		if (gLocalCWD[0] != '\0')
			(void) chdir(gLocalCWD);	/* May not succeed. */
	}

	/* Print a message if we changed the directory. */
	(void) GetCWD(gLocalCWD, sizeof(gLocalCWD));
	if (strcmp(curLocalCWD, gLocalCWD))
		PrintF("Current local directory is %s.\n", gLocalCWD);
}	/* StartupMsg */


/*ARGSUSED*/
static void
SigIntMain(int sigNum)
{
	SIGNAL(SIGINT, SigIntMain);
	EPrintF("\n*Interrupt*\n");
	longjmp(gMainJmp, 1);
}   /* SigIntMain */



void main(int argc, char **argv)
{
	int opt, result;
	OpenOptions openopt;

	Init();
	RunStartupScript();

	GetoptReset();
	gOptErr = 0;
	if (GetOpenOptions(argc, argv, &openopt, 1) == kUsageErr)
		goto usage;

	gOptErr = 1;
	GetoptReset();
	gVisualMode = gDefaultVisualMode;
	while ((opt = Getopt(argc, argv, "aiup:rd:g:cmfGRn:zDLVH")) >= 0) {
		if (strchr("aiup:rd:g:cmfGRn:z", opt) == NULL) {
			switch (opt) {
				case 'D':
					gDebug = kDebuggingOn;
					gTrace = kTracingOn;
					break;
				case 'L': gVisualMode = 0; break;
				case 'V': gVisualMode = 1; break;
				case 'H': VersionCmd(0, NULL); Exit(kExitNoErr); break;
				default:
				usage:
					EPrintF(
		"Usage: ncftp [options] [hostname[:path]]\n");
					EPrintF("Program options:\n\
  -D   : Turn debug mode and trace mode on.\n\
  -L   : Don't use visual mode (use line mode).\n\
  -V   : Use visual mode.\n\
  -H   : Dump the version information.\n");
					EPrintF("Command-line open options:\n\
  -a   : Open anonymously.\n\
  -u   : Open with username and password prompt.\n\
  -p X : Use port number X when opening.\n\
  -r   : Redial until connected.\n\
  -d X : Redial, delaying X seconds between tries.\n\
  -g X : Give up after X redials without connection.\n");
					EPrintF("Command-line retrieve options:\n\
  -f   : Force overwrite.\n\
  -G   : Don't use wildcard matching.\n\
  -R   : Recursive.  Useful for fetching whole directories.\n\
  -n X : Get selected files only if X days old or newer.\n");
					Exit(kExitUsageErr);
			}
		}
	}

	result = kNoErr;
	if (openopt.hostname[0] != '\0') {
		if (setjmp(gMainJmp)) {
			result = kNoErr;
		} else {
			SIGNAL(SIGINT, SigIntMain);
			SIGNAL(SIGPIPE, SIG_IGN);
			if (setjmp(gCmdLoopJmp)) {
				/* May have lost connection during Open. */
				result = kCmdErr;
			} else {
				result = Open(&openopt);
			}
		}
	}

	if (result == kNoErr) {
		InitWindows(gVisualMode && gIsToTTY && gIsFromTTY);
		StartupMsgs();
		(void) RunPrefixedMacro("start.", "ncftp");
		CommandShell();
	}
	DoQuit(kExitNoErr);
	/*NOTREACHED*/
	Exit(kExitNoErr);
}	/* main */

/* eof Main.c */
