/* cmds.c
 *
 * Copyright (c) 1992-2001 by Mike Gleason.
 * All rights reserved.
 *
 */

#include "syshdrs.h"
#include "shell.h"
#include "util.h"
#include "ls.h"
#include "bookmark.h"
#include "cmds.h"
#include "main.h"
#include "trace.h"
#include "log.h"
#include "pref.h"
#include "spool.h"
#include "getline.h"
#include "readln.h"
#include "getopt.h"

/* This was the directory path when the user logged in.  For anonymous users,
 * this should be "/", but for real users, it should be their home directory.
 * This variable is used later when we want to calculate a relative path
 * from the starting directory.
 */
char gStartDir[512];

/* The pathname to the current working directory.  This always needs to be
 * current, so whenever a directory change is done this should be changed.
 */
char gRemoteCWD[512];

/* Same, but the previous directory the user was in, or empty string if
 * there is none.
 */
char gPrevRemoteCWD[512];

/* Another buffer we use just temporarily when switching directories. */
char gScratchCWD[512];

/* The only reason we do this is to get gcc/lint to shut up
 * about unused parameters.
 */
int gUnusedArg;
#define ARGSUSED(x) x = (argc != 0) || (argv != 0) || (cmdp != 0) || (aip != 0)

/* Used temporarily, but put it here because it's big. */
FTPConnectionInfo gTmpURLConn;

/* If the user doesn't want to be prompted for a batch of files,
 * they can tell us to answer this answer for each item in the batch.
 */
int gResumeAnswerAll;

extern FTPLibraryInfo gLib;
extern FTPConnectionInfo gConn;
extern char gOurDirectoryPath[];
extern Command gCommands[];
extern char gVersion[];
extern size_t gNumCommands;
extern int gDebug, gDoneApplication;
extern char *gOptArg;
extern int gOptInd, gGotSig;
extern int gFirstTimeUser;
extern unsigned int gFirewallPort;
extern int gFirewallType;
extern char gFirewallHost[64];
extern char gFirewallUser[32];
extern char gFirewallPass[32];
extern char gFirewallExceptionList[];
extern char gPager[], gHome[], gShell[];
extern char gOS[];
extern int gAutoResume, gRedialDelay;
extern int gAutoSaveChangesToExistingBookmarks;
extern Bookmark gBm;
extern int gLoadedBm, gConfirmClose, gSavePasswords, gScreenColumns;
extern char gLocalCWD[512], gPrevLocalCWD[512], gOurInstallationPath[];
extern int gMayCancelJmp;
#if defined(WIN32) || defined(_WINDOWS)
#elif defined(HAVE_SIGSETJMP)
extern sigjmp_buf gCancelJmp;
#else	/* HAVE_SIGSETJMP */
extern jmp_buf gCancelJmp;
#endif	/* HAVE_SIGSETJMP */




/* Open the users $PAGER, or just return stdout.  Make sure to use
 * ClosePager(), and not fclose/pclose directly.
 */
static FILE *
OpenPager(void)
{
	FILE *fp;
	char *pprog;

	(void) fflush(stdout);
	pprog = gPager;
	fp = popen((pprog[0] == '\0') ? "more" : pprog, "w");
	if (fp == NULL)
		return (stdout);
	return (fp);
}	/* OpenPager */




/* Close (maybe) a file previously created by OpenPager. */
static void
ClosePager(FILE *pagerfp)
{
#ifdef SIGPIPE
	sigproc_t osigpipe;
#endif

	if ((pagerfp != NULL) && (pagerfp != stdout)) {
#ifdef SIGPIPE
		osigpipe = (sigproc_t) NcSignal(SIGPIPE, SIG_IGN);
#endif
		(void) pclose(pagerfp);
#ifdef SIGPIPE
		(void) NcSignal(SIGPIPE, osigpipe);
#endif
	}
}	/* ClosePager */




/* Fills in the bookmarkName field of the Bookmark. */
int
PromptForBookmarkName(BookmarkPtr bmp)
{
	char dfltname[64];
	char bmname[64];

	DefaultBookmarkName(dfltname, sizeof(dfltname), gConn.host);
	if (dfltname[0] == '\0') {
		(void) printf("Enter a name for this bookmark: ");
	} else {
		(void) printf("Enter a name for this bookmark, or hit enter for \"%s\": ", dfltname);
	}
	fflush(stdin);
	(void) FGets(bmname, sizeof(bmname), stdin);
	if (bmname[0] != '\0') {
		(void) STRNCPY(bmp->bookmarkName, bmname);
		return (0);
	} else if (dfltname[0] != '\0') {
		(void) STRNCPY(bmp->bookmarkName, dfltname);
		return (0);
	}
	return (-1);
}	/* PromptForBookmarkName */



void
CurrentURL(char *dst, size_t dsize, int showpass)
{
	Bookmark bm;
	char dir[160];

	memset(&bm, 0, sizeof(bm));
	(void) STRNCPY(bm.name, gConn.host);
	if ((gConn.user[0] != '\0') && (! STREQ(gConn.user, "anonymous")) && (! STREQ(gConn.user, "ftp"))) {
		(void) STRNCPY(bm.user, gConn.user);
		(void) STRNCPY(bm.pass, (showpass == 0) ? "PASSWORD" : gConn.pass);
		(void) STRNCPY(bm.acct, gConn.acct);
	}

	bm.port = gConn.port;

	/* We now save relative paths, because the pathname in URLs are
	 * relative by nature.  This makes non-anonymous FTP URLs shorter
	 * because it doesn't have to include the pathname of their
	 * home directory.
	 */
	(void) STRNCPY(dir, gRemoteCWD);
	AbsoluteToRelative(bm.dir, sizeof(bm.dir), dir, gStartDir, strlen(gStartDir));

	BookmarkToURL(&bm, dst, dsize);
}	/* CurrentURL */




/* Fills in the fields of the Bookmark structure, based on the FTP current
 * session.
 */
void
FillBookmarkInfo(BookmarkPtr bmp)
{
	char dir[160];

	(void) STRNCPY(bmp->name, gConn.host);
	if ((STREQ(gConn.user, "anonymous")) || (STREQ(gConn.user, "ftp"))) {
		bmp->user[0] = '\0';
		bmp->pass[0] = '\0';
		bmp->acct[0] = '\0';
	} else {
		(void) STRNCPY(bmp->user, gConn.user);
		(void) STRNCPY(bmp->pass, gConn.pass);
		(void) STRNCPY(bmp->acct, gConn.acct);
	}

	/* We now save relative paths, because the pathname in URLs are
	 * relative by nature.  This makes non-anonymous FTP URLs shorter
	 * because it doesn't have to include the pathname of their
	 * home directory.
	 */
	(void) STRNCPY(dir, gRemoteCWD);
	AbsoluteToRelative(bmp->dir, sizeof(bmp->dir), dir, gStartDir, strlen(gStartDir));
	bmp->port = gConn.port;
	(void) time(&bmp->lastCall);
	bmp->hasSIZE = gConn.hasSIZE;
	bmp->hasMDTM = gConn.hasMDTM;
	bmp->hasPASV = gConn.hasPASV;
	bmp->hasUTIME = gConn.hasUTIME;
	if (gFirewallType == kFirewallNotInUse)
		(void) STRNCPY(bmp->lastIP, gConn.ip);
}	/* FillBookmarkInfo */




/* Saves the current FTP session settings as a bookmark. */
void
SaveCurrentAsBookmark(void)
{
	int saveBm;
	char ans[64];

	/* gBm.bookmarkName must already be set. */
	FillBookmarkInfo(&gBm);

	saveBm = gSavePasswords;
	if (gLoadedBm != 0)
		saveBm = 1;
	if ((saveBm < 0) && (gBm.pass[0] != '\0')) {
		(void) printf("\n\nYou logged into this site using a password.\nWould you like to save the password with this bookmark?\n\n");
		(void) printf("Save? [no] ");
		(void) memset(ans, 0, sizeof(ans));
		fflush(stdin);
		(void) fgets(ans, sizeof(ans) - 1, stdin);
		if ((saveBm = StrToBool(ans)) == 0) {
			(void) printf("\nNot saving the password.\n");
		}
	}
	if (PutBookmark(&gBm, saveBm) < 0) {
		(void) fprintf(stderr, "Could not save bookmark.\n");
	} else {
		/* Also marks whether we saved it. */
		gLoadedBm = 1;
		(void) printf("Bookmark \"%s\" saved.\n", gBm.bookmarkName);

		ReCacheBookmarks();
	}
}	/* SaveCurrentAsBookmark */




/* If the user did not explicitly bookmark this site already, ask
 * the user if they want to save one.
 */
void
SaveUnsavedBookmark(void)
{
	char url[256];
	char ans[64];
	int c;

	if ((gConfirmClose != 0) && (gLoadedBm == 0) && (gOurDirectoryPath[0] != '\0')) {
		FillBookmarkInfo(&gBm);
		BookmarkToURL(&gBm, url, sizeof(url));
		(void) printf("\n\nYou have not saved a bookmark for this site.\n");
		(void) sleep(1);
		(void) printf("\nWould you like to save a bookmark to:\n\t%s\n\n", url);
		for (;;) {
			(void) printf("Save? (yes/no) ");
			(void) memset(ans, 0, sizeof(ans));
			fflush(stdin);
			if (fgets(ans, sizeof(ans) - 1, stdin) == NULL) {
				c = 'n';
				break;
			}
			c = ans[0];
			if ((c == 'n') || (c == 'y'))
				break;
			if (c == 'N') {
				c = 'n';
				break;
			} else if (c == 'Y') {
				c = 'y';
				break;
			}
		}
		if (c == 'n') {
			(void) printf("Not saved.  (If you don't want to be asked this, \"set confirm-close no\")\n\n\n");

		} else if (PromptForBookmarkName(&gBm) < 0) {
			(void) printf("Nevermind.\n");
		} else {
			SaveCurrentAsBookmark();
		}
	} else if ((gLoadedBm == 1) && (gOurDirectoryPath[0] != '\0') && (strcmp(gOurDirectoryPath, gBm.dir) != 0)) {
		/* Bookmark has changed. */
		if (gAutoSaveChangesToExistingBookmarks != 0) {
			SaveCurrentAsBookmark();
		}
	}
}	/* SaveUnsavedBookmark */



/* Save the current host session settings for later as a "bookmark", which
 * will be referred to by a bookmark abbreviation name.
 */
void
BookmarkCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	/* The only reason we do this is to get gcc/lint to shut up
	 * about unused parameters.
	 */
	ARGSUSED(gUnusedArg);

	if (gOurDirectoryPath[0] == '\0') {
		(void) printf("Sorry, configuration information is not saved for this user.\n");
	} else if ((argc <= 1) || (argv[1][0] == '\0')) {
		/* No name specified on the command line. */
		if (gBm.bookmarkName[0] == '\0') {
			/* Not previously bookmarked. */
			if (PromptForBookmarkName(&gBm) < 0) {
				(void) printf("Nevermind.\n");
			} else {
				SaveCurrentAsBookmark();
			}
		} else {
			/* User wants to update an existing bookmark. */
			SaveCurrentAsBookmark();
		}
	} else {
		(void) STRNCPY(gBm.bookmarkName, argv[1]);
		SaveCurrentAsBookmark();
	}
}	/* BookmarkCmd */




/* Dump a remote file to the screen. */
void
CatCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int result;
	int i;

	ARGSUSED(gUnusedArg);
	for (i=1; i<argc; i++) {
		result = FTPGetOneFile2(&gConn, argv[i], NULL, kTypeAscii, STDOUT_FILENO, kResumeNo, kAppendNo);
		FTPPerror(&gConn, result, kErrCouldNotStartDataTransfer, "cat", argv[i]);
	}
}	/* CatCmd */



static void
NcFTPCdResponseProc(const FTPCIPtr cipUNUSED, ResponsePtr rp)
{
	LinePtr lp;
	LineListPtr llp;

	gUnusedArg = (cipUNUSED != NULL);
	if ((rp->printMode & kResponseNoPrint) != 0)
		return;
	llp = &rp->msg;
	for (lp = llp->first; lp != NULL; lp = lp->next) {
		if ((lp == llp->first) && (rp->codeType == 2)) {
			if (ISTRNCMP(lp->line, "CWD command", 11) == 0)
				continue;
			if (lp->line[0] == '"')
				continue;	/* "/pub/foo" is... */
		}
		(void) printf("%s\n", lp->line);
	}
}	/* NcFTPCdResponseProc */




/* Manually print a response obtained from the remote FTP user. */
void
PrintResp(LineListPtr llp)
{
	LinePtr lp;

	if (llp != NULL) {
		for (lp = llp->first; lp != NULL; lp = lp->next) {
			if ((lp == llp->first) && (ISTRNCMP(lp->line, "CWD command", 11) == 0))
				continue;
			(void) printf("%s\n", lp->line);
		}
	}
}	/* PrintResp */




/* Do a chdir, and update our notion of the current directory.
 * Some servers return it back as part of the CWD response,
 * otherwise do a CWD command followed by a PWD.
 */
int
nFTPChdirAndGetCWD(const FTPCIPtr cip, const char *cdCwd, const int quietMode)
{
	ResponsePtr rp;
	size_t cdCwdLen;
	int result;
#ifdef USE_WHAT_SERVER_SAYS_IS_CWD
	int foundcwd;
	char *l, *r;
#endif

	if (cip == NULL)
		return (kErrBadParameter);
	if (strcmp(cip->magic, kLibraryMagic))
		return (kErrBadMagic);

	if ((cdCwd == NULL) || (cdCwd[0] == '\0')) {
		result = kErrInvalidDirParam;
		cip->errNo = kErrInvalidDirParam;
	} else {
		rp = InitResponse();
		if (rp == NULL) {
			result = kErrMallocFailed;
			cip->errNo = kErrMallocFailed;
			/* Error(cip, kDontPerror, "Malloc failed.\n"); */
		} else {
			cdCwdLen = strlen(cdCwd);
			if (strcmp(cdCwd, "..") == 0) {
				result = RCmd(cip, rp, "CDUP");
			} else {
				result = RCmd(cip, rp, "CWD %s", cdCwd);
			}
			if (result == 2) {
#ifdef USE_WHAT_SERVER_SAYS_IS_CWD
				(void) STRNCPY(gScratchCWD, gRemoteCWD);
				foundcwd = 0;
				if ((r = strrchr(rp->msg.first->line, '"')) != NULL) {
					/* "xxxx" is current directory.
					 * Strip out just the xxxx to copy into the remote cwd.
					 *
					 * This is nice because we didn't have to do a PWD.
					 */
					l = strchr(rp->msg.first->line, '"');
					if ((l != NULL) && (l != r) && (l == rp->msg.first->line)) {
						*r = '\0';
						++l;
						(void) Strncpy(gRemoteCWD, l, sizeof(gRemoteCWD));
						*r = '"';	/* Restore, so response prints correctly. */
						foundcwd = 1;
						result = kNoErr;
					}
				}
				if (quietMode)
					rp->printMode |= kResponseNoPrint;
				NcFTPCdResponseProc(cip, rp);
				DoneWithResponse(cip, rp);
				if (foundcwd == 0) {
					result = FTPGetCWD(cip, gRemoteCWD, sizeof(gRemoteCWD));
					if (result != kNoErr) {
						PathCat(gRemoteCWD, sizeof(gRemoteCWD), gScratchCWD, cdCwd);
						result = kNoErr;
					}
				}
#else /* USE_CLIENT_SIDE_CALCULATED_CWD */
				if (quietMode)
					rp->printMode |= kResponseNoPrint;
				NcFTPCdResponseProc(cip, rp);
				DoneWithResponse(cip, rp);
				(void) STRNCPY(gScratchCWD, gRemoteCWD);
				PathCat(gRemoteCWD, sizeof(gRemoteCWD), gScratchCWD, cdCwd);
				result = kNoErr;
#endif
			} else if (result > 0) {
				result = kErrCWDFailed;
				cip->errNo = kErrCWDFailed;
				DoneWithResponse(cip, rp);
			} else {
				DoneWithResponse(cip, rp);
			}
		}
	}
	return (result);
}	/* nFTPChdirAndGetCWD */




int
Chdirs(FTPCIPtr cip, const char *const cdCwd)
{
	char *cp, *startcp;
	int result;
	int lastSubDir;

	if (cip == NULL)
		return (kErrBadParameter);
	if (strcmp(cip->magic, kLibraryMagic))
		return (kErrBadMagic);

	if (cdCwd == NULL) {
		result = kErrInvalidDirParam;
		cip->errNo = kErrInvalidDirParam;
		return result;
	}

	if ((cdCwd[0] == '\0') || (strcmp(cdCwd, ".") == 0)) {
		result = 0;
		return (result);
	}

	cp = cip->buf;
	cp[cip->bufSize - 2] = '\0';
	if ((cdCwd[0] == '.') && (cdCwd[1] == '.') && ((cdCwd[2] == '\0') || IsLocalPathDelim(cdCwd[2]))) {
		PathCat(cip->buf, cip->bufSize, gRemoteCWD, cdCwd);
	} else {
		(void) Strncpy(cip->buf, cdCwd, cip->bufSize);
	}
	if (cp[cip->bufSize - 2] != '\0')
		return (kErrBadParameter);

	StrRemoveTrailingLocalPathDelim(cp);
	do {
		startcp = cp;
		cp = StrFindLocalPathDelim(cp + 0);
		if (cp != NULL) {
			*cp++ = '\0';
		}
		lastSubDir = (cp == NULL);
		result = nFTPChdirAndGetCWD(cip, (*startcp != '\0') ? startcp : "/", lastSubDir ? 0 : 1);
		if (result < 0) {
			cip->errNo = result;
		}
	} while ((!lastSubDir) && (result == 0));

	return (result);
}	/* Chdirs */




/* Remote change of working directory command. */
void
ChdirCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int result;
	LineList ll;
	LinePtr lp;

	ARGSUSED(gUnusedArg);

	if (argc <= 1) {
		if (gStartDir[0] != '\0') {
			(void) STRNCPY(gPrevRemoteCWD, gRemoteCWD);
			result = Chdirs(&gConn, gStartDir);
			if (result != kNoErr) {
				/* State is incoherent if this happens! */
				FTPPerror(&gConn, result, kErrCWDFailed, "Could not chdir to", gStartDir);
			}
		} else {
			PrintCmdUsage(cmdp);
		}
	} else {
		InitLineList(&ll);
		result = FTPRemoteGlob(&gConn, &ll, argv[1], (aip->noglobargv[1] != 0) ? kGlobNo: kGlobYes);
		if (result < 0) {
			FTPPerror(&gConn, result, kErrGlobFailed, argv[0], argv[1]);
		} else {
			lp = ll.first;
			if ((lp != NULL) && (lp->line != NULL)) {
				if ((strcmp(lp->line, "-") == 0) && (gPrevRemoteCWD[0] != '\0')) {
					free(lp->line);
					lp->line = StrDup(gPrevRemoteCWD);
					if (lp->line == NULL) {
						result = kErrMallocFailed;
						gConn.errNo = kErrMallocFailed;
					} else {
						(void) STRNCPY(gPrevRemoteCWD, gRemoteCWD);
						result = Chdirs(&gConn, lp->line);
					}
				} else {
					(void) STRNCPY(gPrevRemoteCWD, gRemoteCWD);
					result = Chdirs(&gConn, lp->line);
				}
				if (result != kNoErr)
					FTPPerror(&gConn, result, kErrCWDFailed, "Could not chdir to", lp->line);
			}
		}
		DisposeLineListContents(&ll);
	}
}	/* ChdirCmd */




/* Chmod files on the remote host, if it supports it. */
void
ChmodCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int i, result;

	ARGSUSED(gUnusedArg);
	for (i=2; i<argc; i++) {
		result = FTPChmod(
				&gConn, argv[i], argv[1],
				(aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes
			);
		if (result < 0) {
			FTPPerror(&gConn, result, kErrChmodFailed, "chmod", argv[i]);
			/* but continue */
		}
	}

	/* Really should just flush only the modified directories... */
	FlushLsCache();
}	/* ChmodCmd */




/* Close the current session to a remote FTP server. */
void
CloseCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	ARGSUSED(gUnusedArg);
	if (gConn.connected == 0)
		(void) printf("Already closed.\n");
	else
		CloseHost();
}	/* CloseCmd */



/* User interface to the program's debug-mode setting. */
void
DebugCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	ARGSUSED(gUnusedArg);
	if (argc > 1)
		SetDebug(atoi(argv[1]));
	else
		SetDebug(!gDebug);
}	/* DebugCmd */




/* Delete files on the remote host. */
void
DeleteCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int result;
	int i, c;
	int recursive = kRecursiveNo;

	ARGSUSED(gUnusedArg);
	GetoptReset();
	while ((c = Getopt(argc, argv, "rf")) > 0) switch(c) {
		case 'r':
			recursive = kRecursiveYes;
			break;
		case 'f':
			/* ignore */
			break;
		default:
			PrintCmdUsage(cmdp);
			return;
	}

	for (i=gOptInd; i<argc; i++) {
		result = FTPDelete(
				&gConn, argv[i], recursive,
				(aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes
			);
		if (result < 0) {
			FTPPerror(&gConn, result, kErrDELEFailed, "delete", argv[i]);
			/* but continue */
		}
	}

	/* Really should just flush only the modified directories... */
	FlushLsCache();
}	/* DeleteCmd */




/* Command shell echo command.  This is mostly useful for testing the command
 * shell, as a sample command which prints some output.
 */
void
EchoCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int i;
	int result;
	int np = 0;
	LineList ll;
	LinePtr lp;

	ARGSUSED(gUnusedArg);
	for (i=1; i<argc; i++) {
		InitLineList(&ll);
		result = FTPLocalGlob(&gConn, &ll, argv[i], (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
		if (result < 0) {
			FTPPerror(&gConn, result, kErrGlobFailed, "local glob", argv[i]);
		} else {
			for (lp = ll.first; lp != NULL; lp = lp->next) {
				if (lp->line != NULL) {
					if (np > 0)
						(void) printf(" ");
					(void) printf("%s", lp->line);
					np++;
				}
			}
		}
		DisposeLineListContents(&ll);
	}
	(void) printf("\n");
}	/* EchoCmd */




static int
NcFTPConfirmResumeDownloadProc(
	const char *volatile *localpath,
	volatile longest_int localsize,
	volatile time_t localmtime,
	const char *volatile remotepath,
	volatile longest_int remotesize,
	volatile time_t remotemtime,
	volatile longest_int *volatile startPoint)
{
	int zaction = kConfirmResumeProcSaidBestGuess;
	char tstr[80], ans[32];
	static char newname[128];	/* arrggh... static. */

	if (gResumeAnswerAll != kConfirmResumeProcNotUsed)
		return (gResumeAnswerAll);

	if (gAutoResume != 0)
		return (kConfirmResumeProcSaidBestGuess);

	tstr[sizeof(tstr) - 1] = '\0';
	(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &localmtime));
	(void) printf(
#if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
		"\nThe local file \"%s\" already exists.\n\tLocal:  %12lld bytes, dated %s.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
		"\nThe local file \"%s\" already exists.\n\tLocal:  %12qd bytes, dated %s.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
		"\nThe local file \"%s\" already exists.\n\tLocal:  %12I64d bytes, dated %s.\n",
#else
		"\nThe local file \"%s\" already exists.\n\tLocal:  %12ld bytes, dated %s.\n",
#endif
		*localpath,
		localsize,
		tstr
	);

	if ((remotemtime != kModTimeUnknown) && (remotesize != kSizeUnknown)) {
		(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &remotemtime));
		(void) printf(
#if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
			"\tRemote: %12lld bytes, dated %s.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
			"\tRemote: %12qd bytes, dated %s.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
			"\tRemote: %12I64d bytes, dated %s.\n",
#else
			"\tRemote: %12ld bytes, dated %s.\n",
#endif
			remotesize,
			tstr
		);
		if ((remotemtime == localmtime) && (remotesize == localsize)) {
			(void) printf("\t(Files are identical, skipped)\n\n");
			return (kConfirmResumeProcSaidSkip);
		}
	} else if (remotesize != kSizeUnknown) {
		(void) printf(
#if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
			"\tRemote: %12lld bytes, date unknown.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
			"\tRemote: %12qd bytes, date unknown.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
			"\tRemote: %12I64d bytes, date unknown.\n",
#else
			"\tRemote: %12ld bytes, date unknown.\n",
#endif
			remotesize
		);
	} else if (remotemtime != kModTimeUnknown) {
		(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &remotemtime));
		(void) printf(
			"\tRemote: size unknown, dated %s.\n",
			tstr
		);
	}

	printf("\n");
	(void) memset(ans, 0, sizeof(ans));
	for (;;) {
		(void) printf("\t[O]verwrite?");
		if ((gConn.hasREST == kCommandAvailable) && (remotesize != kSizeUnknown) && (remotesize > localsize))
			printf("  [R]esume?");
		printf("  [A]ppend to?  [S]kip?  [N]ew Name?\n");
		(void) printf("\t[O!]verwrite all?");
		if ((gConn.hasREST == kCommandAvailable) && (remotesize != kSizeUnknown) && (remotesize > localsize))
			printf("  [R!]esume all?");
		printf("  [S!]kip all?  [C]ancel  > ");
		fflush(stdin);
		(void) fgets(ans, sizeof(ans) - 1, stdin);
		switch ((int) ans[0]) {
			case 'c':
			case 'C':
				ans[0] = 'C';
				ans[1] = '\0';
				zaction = kConfirmResumeProcSaidCancel;
				break;
			case 'o':
			case 'O':
				ans[0] = 'O';
				zaction = kConfirmResumeProcSaidOverwrite;
				break;
			case 'r':
			case 'R':
				if ((gConn.hasREST != kCommandAvailable) || (remotesize == kSizeUnknown)) {
					printf("\tResume is not available on this server.\n\n");
					ans[0] = '\0';
					break;
				} else if (remotesize < localsize) {
					printf("\tCannot resume when local file is already larger than the remote file.\n\n");
					ans[0] = '\0';
					break;
				} else if (remotesize <= localsize) {
					printf("\tLocal file is already the same size as the remote file.\n\n");
					ans[0] = '\0';
					break;
				}
				ans[0] = 'R';
				*startPoint = localsize;
				zaction = kConfirmResumeProcSaidResume;
				if (OneTimeMessage("auto-resume") != 0) {
					printf("\n\tNOTE: If you want NcFTP to guess automatically, \"set auto-resume yes\"\n\n");
				}
				break;
			case 's':
			case 'S':
				ans[0] = 'S';
				zaction = kConfirmResumeProcSaidSkip;
				break;
			case 'n':
			case 'N':
				ans[0] = 'N';
				ans[1] = '\0';
				zaction = kConfirmResumeProcSaidOverwrite;
				break;
			case 'a':
			case 'A':
				ans[0] = 'A';
				ans[1] = '\0';
				zaction = kConfirmResumeProcSaidAppend;
				break;
			case 'g':
			case 'G':
				ans[0] = 'G';
				zaction = kConfirmResumeProcSaidBestGuess;
				break;
			default:
				ans[0] = '\0';
		}
		if (ans[0] != '\0')
			break;
	}

	if (ans[0] == 'N') {
		(void) memset(newname, 0, sizeof(newname));
		printf("\tSave as:  ");
		fflush(stdin);
		(void) fgets(newname, sizeof(newname) - 1, stdin);
		newname[strlen(newname) - 1] = '\0';
		if (newname[0] == '\0') {
			/* Nevermind. */
			printf("Skipped %s.\n", remotepath);
			zaction = kConfirmResumeProcSaidSkip;
		} else {
			*localpath = newname;
		}
	}

	if (ans[1] == '!')
		gResumeAnswerAll = zaction;
	return (zaction);
}	/* NcFTPConfirmResumeDownloadProc */




/* Download files from the remote system. */
void
GetCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int opt;
	int renameMode = 0;
	int recurseFlag = kRecursiveNo;
	int appendFlag = kAppendNo;
	int resumeFlag = kResumeYes;
	int tarflag = kTarYes;
	const char *dstdir = NULL;
	int rc;
	int i;
	int doGlob;
	int xtype = gBm.xferType;
	int nD = 0;
	int deleteFlag = kDeleteNo;
	char pattern[256];
	vsigproc_t osigint;
	ConfirmResumeDownloadProc confirmProc;

	confirmProc = NcFTPConfirmResumeDownloadProc;
	gResumeAnswerAll = kConfirmResumeProcNotUsed;	/* Ask at least once each time */
	ARGSUSED(gUnusedArg);
	GetoptReset();
	while ((opt = Getopt(argc, argv, "aAzfrRTD")) >= 0) switch (opt) {
		case 'a':
			xtype = kTypeAscii;
			break;
		case 'A':
			/* Append to local files, instead of truncating
			 * them first.
			 */
			appendFlag = kAppendYes;
			break;
		case 'f':
		case 'Z':
			/* Do not try to resume a download, even if it
			 * appeared that some of the file was transferred
			 * already.
			 */
			resumeFlag = kResumeNo;
			confirmProc = NoConfirmResumeDownloadProc;
			break;
		case 'z':
			/* Special flag that lets you specify the
			 * destination file.  Normally a "get" will
			 * write the file by the same name as the
			 * remote file's basename.
			 */
			renameMode = 1;
			break;
		case 'r':
		case 'R':
			/* If the item is a directory, get the
			 * directory and all its contents.
			 */
			recurseFlag = kRecursiveYes;
			break;
		case 'T':
			/* If they said "-R", they may want to
			 * turn off TAR mode if they are trying
			 * to resume the whole directory.
			 * The disadvantage to TAR mode is that
			 * it always downloads the whole thing,
			 * which is why there is a flag to
			 * disable this.
			 */
			tarflag = kTarNo;
			break;
		case 'D':
			/* You can delete the remote file after
			 * you downloaded it successfully by using
			 * the -DD option.  It requires two -D's
			 * to minimize the odds of accidentally
			 * using a single -D.
			 */
			nD++;
			break;
		default:
			PrintCmdUsage(cmdp);
			return;
	}

	if (nD >= 2)
		deleteFlag = kDeleteYes;

	if (renameMode != 0) {
		if (gOptInd > argc - 2) {
			PrintCmdUsage(cmdp);
			(void) fprintf(stderr, "\nFor get with rename, try \"get -z remote-path-name local-path-name\".\n");
			return;
		}
		osigint = NcSignal(SIGINT, XferCanceller);
		rc = FTPGetOneFile3(&gConn, argv[gOptInd], argv[gOptInd + 1], xtype, (-1), resumeFlag, appendFlag, deleteFlag, NoConfirmResumeDownloadProc, 0);
		if (rc < 0)
			FTPPerror(&gConn, rc, kErrCouldNotStartDataTransfer, "get", argv[gOptInd]);
	} else {
		osigint = NcSignal(SIGINT, XferCanceller);
		for (i=gOptInd; i<argc; i++) {
			doGlob = (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes;
			STRNCPY(pattern, argv[i]);
			StrRemoveTrailingSlashes(pattern);
			rc = FTPGetFiles3(&gConn, pattern, dstdir, recurseFlag, doGlob, xtype, resumeFlag, appendFlag, deleteFlag, tarflag, confirmProc, 0);
			if (rc < 0)
				FTPPerror(&gConn, rc, kErrCouldNotStartDataTransfer, "get", argv[i]);
		}
	}
	(void) NcSignal(SIGINT, osigint);
	(void) fflush(stdin);

	if (deleteFlag == kDeleteYes) {
		/* Directory is now out of date */
		FlushLsCache();
	}
}	/* GetCmd */




/* Display some brief help for specified commands, or a list of commands. */
void
HelpCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	CommandPtr c;
	int showall = 0, helpall = 0;
	int i, j, k, n;
	int nRows, nCols;
	int nCmds2Print;
	int screenColumns;
	int len, widestName;
	char *cp, spec[16];
	const char *cmdnames[64];

	ARGSUSED(gUnusedArg);
	assert(gNumCommands < (sizeof(cmdnames) / sizeof(char *)));
	if (argc == 2) {
		showall = (strcmp(argv[1], "showall") == 0);
		helpall = (strcmp(argv[1], "helpall") == 0);
	}
	if (argc == 1 || showall) {
		(void) printf("\
Commands may be abbreviated.  'help showall' shows hidden and unsupported \n\
commands.  'help <command>' gives a brief description of <command>.\n\n");

		/* First, see how many commands we will be printing to the screen.
		 * Unless 'showall' was given, we won't be printing the hidden
		 * (i.e. not very useful to the end-user) commands.
		 */
		c = gCommands;
		nCmds2Print = 0;
		for (n = 0; n < (int) gNumCommands; c++, n++)
			if ((!iscntrl((int) c->name[0])) && (!(c->flags & kCmdHidden) || showall))
				nCmds2Print++;

		(void) memset((char *) cmdnames, 0, sizeof(cmdnames));

		/* Now form the list we'll be printing, and noting what the maximum
		 * length of a command name was, so we can use that when determining
		 * how to print in columns.
		 */
		c = gCommands;
		i = 0;
		widestName = 0;
		for (n = 0; n < (int) gNumCommands; c++, n++) {
			if ((!iscntrl((int) c->name[0])) && (!(c->flags & kCmdHidden) || showall)) {
				cmdnames[i++] = c->name;
				len = (int) strlen(c->name);
				if (len > widestName)
					widestName = len;
			}
		}

		if ((cp = (char *) getenv("COLUMNS")) == NULL)
			screenColumns = 80;
		else
			screenColumns = atoi(cp);

		/* Leave an extra bit of whitespace for the margins between columns. */
		widestName += 2;

		nCols = (screenColumns + 0) / widestName;
		nRows = nCmds2Print / nCols;
		if ((nCmds2Print % nCols) > 0)
			nRows++;

		for (i = 0; i < nRows; i++) {
			for (j = 0; j < nCols; j++) {
				k = nRows * j + i;
				if (k < nCmds2Print) {
					(void) sprintf(spec, "%%-%ds",
						(j < nCols - 1) ? widestName : widestName - 2
					);
					(void) printf(spec, cmdnames[k]);
				}
			}
			(void) printf("\n");
		}
	} else if (helpall) {
		/* Really intended for me, so I can debug the help strings. */
		for (c = gCommands, n = 0; n < (int) gNumCommands; c++, n++) {
			PrintCmdHelp(c);
			PrintCmdUsage(c);
		}
	} else {
		/* For each command name specified, print its help stuff. */
		for (i=1; i<argc; i++) {
			c = GetCommandByName(argv[i], 0);
			if (c == kAmbiguousCommand) {
				(void) printf("%s: ambiguous command name.\n", argv[i]);
			} else if (c == kNoCommand) {
				(void) printf("%s: no such command.\n", argv[i]);
			} else {
				if (i > 1)
					(void) printf("\n");
				PrintCmdHelp(c);
				PrintCmdUsage(c);
			}
		}
	}
}	/* HelpCmd */




/* Displays the list of saved bookmarks, so that the user can then choose
 * one by name.
 */
static int
PrintHosts(void)
{
	FILE *bookmarks;
	FILE *pager;
	int nbm = 0;
	Bookmark bm;
	char url[128];
#ifdef SIGPIPE
	sigproc_t osigpipe;
#endif

	bookmarks = OpenBookmarkFile(NULL);
	if (bookmarks == NULL)
		return (0);

#ifdef SIGPIPE
	osigpipe = (sigproc_t) NcSignal(SIGPIPE, SIG_IGN);
#endif
	pager = OpenPager();

	while (GetNextBookmark(bookmarks, &bm) == 0) {
		nbm++;
		if (nbm == 1) {
			/* header */
			(void) fprintf(pager, "--BOOKMARK----------URL--------------------------------------------------------\n");
		}
		BookmarkToURL(&bm, url, sizeof(url));
		(void) fprintf(pager, "  %-16s  %s\n", bm.bookmarkName, url);
	}

	ClosePager(pager);
	CloseBookmarkFile(bookmarks);

#ifdef SIGPIPE
	(void) NcSignal(SIGPIPE, osigpipe);
#endif
	return (nbm);
}	/* PrintHosts */





static int
RunBookmarkEditor(char *selectedBmName, size_t dsize)
{
#if defined(WIN32) || defined(_WINDOWS)
	char ncftpbookmarks[260];
	const char *prog;
	int winExecResult;
	HANDLE hMailSlot;
	char msg[kNcFTPBookmarksMailslotMsgSize];
	DWORD dwRead;
	BOOL rc;

	if (selectedBmName != NULL)
		memset(selectedBmName, 0, dsize);
	if (gOurInstallationPath[0] == '\0') {
		(void) fprintf(stderr, "Cannot find path to %s.  Please re-run Setup.\n", "ncftpbookmarks.exe");
		return (-1);
	}
	prog = ncftpbookmarks;
	OurInstallationPath(ncftpbookmarks, sizeof(ncftpbookmarks), "ncftpbookmarks.exe");


	hMailSlot = CreateMailslot(kNcFTPBookmarksMailslot, kNcFTPBookmarksMailslotMsgSize, MAILSLOT_WAIT_FOREVER, NULL);

	if (hMailSlot == INVALID_HANDLE_VALUE) {
		SysPerror("CreateMailslot");
		(void) fprintf(stderr, "Could not create communication channel with %s.\n", "ncftpbookmarks.exe");
		(void) fprintf(stderr, "%s", "This means if you select a bookmark to connect to that NcFTP\n");
		(void) fprintf(stderr, "%s", "will not get the message from %s.\n");
	}

	winExecResult = WinExec(prog, SW_SHOWNORMAL);
	if (winExecResult <= 31) switch (winExecResult) {
		case ERROR_BAD_FORMAT:
			fprintf(stderr, "Could not run %s: %s\n", prog, "The .EXE file is invalid");
			return (-1);
		case ERROR_FILE_NOT_FOUND:
			fprintf(stderr, "Could not run %s: %s\n", prog, "The specified file was not found.");
			return (-1);
		case ERROR_PATH_NOT_FOUND:
			fprintf(stderr, "Could not run %s: %s\n", prog, "The specified path was not found.");
			return (-1);
		default:
			fprintf(stderr, "Could not run %s: Unknown error #%d.\n", prog, winExecResult);
			return (-1);
	}

	if (hMailSlot != INVALID_HANDLE_VALUE) {
		fprintf(stdout, "Waiting for %s to exit...\n", "ncftpbookmarks.exe");
		ZeroMemory(msg, sizeof(msg));
		dwRead = 0;
		rc = ReadFile(
			hMailSlot,
			msg,
			sizeof(msg),
			&dwRead,
			NULL
			);

		if (!rc) {
			SysPerror("ReadFile");
		} else {
			msg[sizeof(msg) - 1] = '\0';
			Strncpy(selectedBmName, msg, dsize);
			Trace(0, "Selected bookmark from editor: [%s]\n", selectedBmName);
		}
		CloseHandle(hMailSlot);
	}
	return (0);

#else
#ifdef BINDIR
	char ncftpbookmarks[256];
	char *av[8];
	int pid;
	int status;
	char bmSelectionFile[256];
	char pidStr[32];
	FILE *fp;

	if (selectedBmName != NULL)
		memset(selectedBmName, 0, dsize);
	STRNCPY(ncftpbookmarks, BINDIR);
	STRNCAT(ncftpbookmarks, "/");
	STRNCAT(ncftpbookmarks, "ncftpbookmarks");

	STRNCPY(bmSelectionFile, "view");
	if ((selectedBmName != NULL) && (gOurDirectoryPath[0] != '\0')) {
		sprintf(pidStr, ".%u", (unsigned int) getpid());
		OurDirectoryPath(bmSelectionFile, sizeof(bmSelectionFile), kOpenSelectedBookmarkFileName);
		STRNCAT(bmSelectionFile, pidStr);
	}

	if (access(ncftpbookmarks, X_OK) == 0) {
		pid = (int) fork();
		if (pid < 0) {
			return (-1);
		} else if (pid == 0) {
			/* child */

			av[0] = (char *) "ncftpbookmarks";
			av[1] = bmSelectionFile;
			av[2] = NULL;
			execv(ncftpbookmarks, av);
			exit(1);
		} else {
			/* parent NcFTP */
			for (;;) {
#ifdef HAVE_WAITPID
				if ((waitpid(pid, &status, 0) < 0) && (errno != EINTR))
					break;
#else
				if ((wait(&status) < 0) && (errno != EINTR))
					break;
#endif
				if (WIFEXITED(status) || WIFSIGNALED(status))
					break;		/* done */
			}

			if (strcmp(bmSelectionFile, "view") != 0) {
				fp = fopen(bmSelectionFile, FOPEN_READ_TEXT);
				if (fp != NULL) {
					(void) FGets(selectedBmName, dsize, fp);
					(void) fclose(fp);
					(void) unlink(bmSelectionFile);
					Trace(0, "Selected bookmark from editor: [%s]\n", selectedBmName);
				}
			}
			return (0);
		}
	}
	return (-1);
#else	/* BINDIR */
	/* Not installed. */
	return (-1);
#endif	/* BINDIR */
#endif	/* Windows */
}	/* RunBookmarkEditor */



/* This just shows the list of saved bookmarks. */
void
HostsCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	const char *av[3];
	char bm[128];

	ARGSUSED(gUnusedArg);
	/* Skip visual mode if "-l". */
	if ((argc == 1) && (RunBookmarkEditor(bm, sizeof(bm)) == 0)) {
		if (bm[0] != '\0') {
			av[0] = "open";
			av[1] = bm;
			av[2] = NULL;
			OpenCmd(2, av, (CommandPtr) 0, (ArgvInfoPtr) 0);
		}
		return;
	}
	if (PrintHosts() <= 0) {
		(void) printf("You haven't bookmarked any FTP sites.\n");
		(void) printf("Before closing a site, you can use the \"bookmark\" command to save the current\nhost and directory for next time.\n");
	} else {
		(void) printf("\nTo use a bookmark, use the \"open\" command with the name of the bookmark.\n");
	}
}	/* HostsCmd */




/* Show active background ncftp (ncftpbatch) jobs. */
void
JobsCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	ARGSUSED(gUnusedArg);
	Jobs();
}	/* JobsCmd */




/* Do a "ls" on the remote system and display the directory contents to our
 * user.  This command handles "dir" (ls -l), "ls", and "nlist" (ls -1).
 */
void
ListCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	volatile int i;
	int j;
	char options[32];
	char option[2];
	volatile int listmode;
	FILE *stream;
	volatile int paging;
#if defined(WIN32) || defined(_WINDOWS)
#else
	int sj;
	vsigproc_t osigpipe, osigint;
#endif

	ARGSUSED(gUnusedArg);
	stream = stdout;
	paging = 0;

	if (argv[0][0] == 'd') {
		/* dir */
		listmode = 'l';
	} else if (argv[0][0] == 'n') {
		/* nlist */
		listmode = '1';
	} else if (argv[0][0] == 'p') {
		/* pager */
		paging = 1;

		if (argv[0][1] == 'd') {
			/* dir */
			listmode = 'l';
		} else if (argv[0][1] == 'n') {
			/* nlist */
			listmode = '1';
		} else {
			/* ls */
			listmode = 'C';
		}
	} else {
		/* ls */
		listmode = 'C';
	}
	options[0] = '\0';
	option[1] = '\0';

	for (i=1; i<argc; i++) {
		if (argv[i][0] != '-')
			break;
		if (argv[i][1] == '-') {
			if (argv[i][2] == '\0') {
				/* end of options. */
				++i;
				break;
			} else {
				/* GNU-esque long --option? */
				PrintCmdUsage(cmdp);
			}
		} else {
			for (j=1; ; j++) {
				option[0] = argv[i][j];
				if (argv[i][j] == '\0')
					break;
				switch (argv[i][j]) {
					case 'l':
						listmode = 'l';
						break;
					case '1':
						listmode = '1';
						break;
					case 'C':
						listmode = 'C';
						break;
					default:
						(void) STRNCAT(options, option);
						break;
				}
			}
		}
	}


	if (paging != 0) {
		stream = OpenPager();
		if (stream == NULL) {
			return;
		}

#if defined(WIN32) || defined(_WINDOWS)
#elif defined(HAVE_SIGSETJMP)
		osigpipe = osigint = (sigproc_t) 0;
		sj = sigsetjmp(gCancelJmp, 1);
#else	/* HAVE_SIGSETJMP */
		osigpipe = osigint = (sigproc_t) 0;
		sj = setjmp(gCancelJmp);
#endif	/* HAVE_SIGSETJMP */

#if defined(WIN32) || defined(_WINDOWS)
#else
		if (sj != 0) {
			/* Caught a signal. */
			(void) NcSignal(SIGPIPE, (FTPSigProc) SIG_IGN);
			ClosePager(stream);
			(void) NcSignal(SIGPIPE, osigpipe);
			(void) NcSignal(SIGINT, osigint);
			Trace(0, "Canceled because of signal %d.\n", gGotSig);
			(void) fprintf(stderr, "Canceled.\n");
			gMayCancelJmp = 0;
			return;
		} else {
			osigpipe = NcSignal(SIGPIPE, Cancel);
			osigint = NcSignal(SIGINT, Cancel);
			gMayCancelJmp = 1;
		}
#endif
	}

	if (argv[i] == NULL) {
		/* No directory specified, use cwd. */
		Ls(NULL, listmode, options, stream);
	} else {
		/* List each item. */
		for ( ; i<argc; i++) {
			Ls(argv[i], listmode, options, stream);
		}
	}

	if (paging != 0) {
		ClosePager(stream);
#if defined(WIN32) || defined(_WINDOWS)
#else
		(void) NcSignal(SIGPIPE, osigpipe);
		(void) NcSignal(SIGINT, osigint);
#endif
	}
	gMayCancelJmp = 0;
}	/* ListCmd */




/* Does a change of working directory on the local host. */
void
LocalChdirCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int result;
	const char *dir;
	char buf[512];

	ARGSUSED(gUnusedArg);
	dir = ((argc < 2) ? gHome : argv[1]);
	if ((dir[0] == '-') && (dir[1] == '\0')) {
		if (gPrevLocalCWD[0] == '\0') {
			(void) fprintf(stderr, "No previous local working directory to switch to.\n");
			return;
		} else {
			dir = gPrevLocalCWD;
		}
	} else if (dir[0] == '~') {
		if (dir[1] == '\0') {
			dir = gHome;
		} else if (dir[1] == '/') {
			(void) STRNCPY(buf, gHome);
			dir = STRNCAT(buf, dir + 1);
		}
	}
	result = chdir(dir);
	if (result < 0) {
		perror(dir);
	} else {
		(void) STRNCPY(gPrevLocalCWD, gLocalCWD);
		(void) FTPGetLocalCWD(gLocalCWD, sizeof(gLocalCWD));

	}
}	/* LocalChdirCmd */




/* Does directory listing on the local host. */
void
LocalListCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
#if defined(WIN32) || defined(_WINDOWS)
	volatile int i;
	int j;
	char options[32];
	char option[2];
	volatile int listmode;
	FILE *stream;
	volatile int paging;


	ARGSUSED(gUnusedArg);
	stream = stdout;
	paging = 0;

	if (argv[0][1] == 'd') {
		/* dir */
		listmode = 'l';
	} else if (argv[0][1] == 'n') {
		/* nlist */
		listmode = '1';
	} else {
		/* ls */
		listmode = 'C';
	}
	options[0] = '\0';
	option[1] = '\0';

	for (i=1; i<argc; i++) {
		if (argv[i][0] != '-')
			break;
		if (argv[i][1] == '-') {
			if (argv[i][2] == '\0') {
				/* end of options. */
				++i;
				break;
			} else {
				/* GNU-esque long --option? */
				PrintCmdUsage(cmdp);
			}
		} else {
			for (j=1; ; j++) {
				option[0] = argv[i][j];
				if (argv[i][j] == '\0')
					break;
				switch (argv[i][j]) {
					case 'l':
						listmode = 'l';
						break;
					case '1':
						listmode = '1';
						break;
					case 'C':
						listmode = 'C';
						break;
					default:
						(void) STRNCAT(options, option);
						break;
				}
			}
		}
	}


	if (argv[i] == NULL) {
		/* No directory specified, use cwd. */
		LLs(NULL, listmode, options, stream);
	} else {
		/* List each item. */
		for ( ; i<argc; i++) {
			LLs(argv[i], listmode, options, stream);
		}
	}

#else
	FILE *volatile outfp;
	FILE *volatile infp;
	int i;
	int sj;
	int dashopts;
	char incmd[256];
	char line[256];
	vsigproc_t osigpipe, osigint;

	ARGSUSED(gUnusedArg);
	(void) fflush(stdin);
	outfp = OpenPager();

	(void) STRNCPY(incmd, "/bin/ls");
	for (i=1, dashopts=0; i<argc; i++) {
		(void) STRNCAT(incmd, " ");
		if (argv[i][0] == '-')
			dashopts++;
		(void) STRNCAT(incmd, argv[i]);
	}

	if (dashopts == 0) {
		(void) STRNCPY(incmd, "/bin/ls -CF");
		for (i=1; i<argc; i++) {
			(void) STRNCAT(incmd, " ");
			(void) STRNCAT(incmd, argv[i]);
		}
	}

	infp = popen(incmd, "r");
	if (infp == NULL) {
		ClosePager(outfp);
		return;
	}


#ifdef HAVE_SIGSETJMP
	sj = sigsetjmp(gCancelJmp, 1);
#else	/* HAVE_SIGSETJMP */
	sj = setjmp(gCancelJmp);
#endif	/* HAVE_SIGSETJMP */

	if (sj != 0) {
		/* Caught a signal. */
		(void) NcSignal(SIGPIPE, (FTPSigProc) SIG_IGN);
		ClosePager(outfp);
		if (infp != NULL)
			(void) pclose(infp);
		(void) NcSignal(SIGPIPE, osigpipe);
		(void) NcSignal(SIGINT, osigint);
		(void) fprintf(stderr, "Canceled.\n");
		Trace(0, "Canceled because of signal %d.\n", gGotSig);
		gMayCancelJmp = 0;
		return;
	} else {
		osigpipe = NcSignal(SIGPIPE, Cancel);
		osigint = NcSignal(SIGINT, Cancel);
		gMayCancelJmp = 1;
	}

	while (fgets(line, sizeof(line) - 1, infp) != NULL)
		(void) fputs(line, outfp);
	(void) fflush(outfp);

	(void) pclose(infp);
	infp = NULL;
	ClosePager(outfp);
	outfp = NULL;

	(void) NcSignal(SIGPIPE, osigpipe);
	(void) NcSignal(SIGINT, osigint);
	gMayCancelJmp = 0;
#endif
}	/* LocalListCmd */




/* static void
Sys(const int argc, const char **const argv, const ArgvInfoPtr aip, const char *syscmd, int noDQuote)
{
	char cmd[256];
	int i;

	(void) STRNCPY(cmd, syscmd);
	for (i = 1; i < argc; i++) {
		if (aip->noglobargv[i] != 0) {
			(void) STRNCAT(cmd, " '");
			(void) STRNCAT(cmd, argv[i]);
			(void) STRNCAT(cmd, "'");
		} else if (noDQuote != 0) {
			(void) STRNCAT(cmd, " ");
			(void) STRNCAT(cmd, argv[i]);
		} else {
			(void) STRNCAT(cmd, " \"");
			(void) STRNCAT(cmd, argv[i]);
			(void) STRNCAT(cmd, "\" ");
		}
	}
#if defined(WIN32) || defined(_WINDOWS)
	fprintf(stderr, "Cannot run command: %s\n", cmd);
#else
	Trace(0, "Sys: %s\n", cmd);
	(void) system(cmd);
#endif
}*/




#if defined(WIN32) || defined(_WINDOWS)
#else
void
LocalChmodCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	ARGSUSED(gUnusedArg);
	Sys(argc, argv, aip, "/bin/chmod", 1);
}	/* LocalChmodCmd */
#endif



void
LocalMkdirCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
#if defined(WIN32) || defined(_WINDOWS)
	const char *arg;
	int i;

	for (i = 1; i < argc; i++) {
		arg = argv[i];
		if (MkDirs(arg, 00755) < 0) {
			perror(arg);
		}
	}
#else
	ARGSUSED(gUnusedArg);
	Sys(argc, argv, aip, "/bin/mkdir", 0);
#endif
}	/* LocalMkdirCmd */




#if defined(WIN32) || defined(_WINDOWS)
#else
void
LocalPageCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	ARGSUSED(gUnusedArg);
	Sys(argc, argv, aip, gPager, 0);
}	/* LocalPageCmd */
#endif




void
LocalRenameCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
#if defined(WIN32) || defined(_WINDOWS)
	if (rename(argv[1], argv[2]) < 0) {
		perror("rename");
	}
#else
	ARGSUSED(gUnusedArg);
	Sys(argc, argv, aip, "/bin/mv", 1);
#endif
}	/* LocalRenameCmd */




void
LocalRmCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
#if defined(WIN32) || defined(_WINDOWS)
	int i;
	int result;
	LineList ll;
	LinePtr lp;

	ARGSUSED(gUnusedArg);
	for (i=1; i<argc; i++) {
		InitLineList(&ll);
		result = FTPLocalGlob(&gConn, &ll, argv[i], (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
		if (result < 0) {
			FTPPerror(&gConn, result, kErrGlobFailed, "local glob", argv[i]);
		} else {
			for (lp = ll.first; lp != NULL; lp = lp->next) {
				if (lp->line != NULL) {
					if (remove(lp->line) < 0)
						perror(lp->line);
				}
			}
		}
		DisposeLineListContents(&ll);
	}
#else
	ARGSUSED(gUnusedArg);
	Sys(argc, argv, aip, "/bin/rm", 1);
#endif
}	/* LocalRmCmd */




void
LocalRmdirCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
#if defined(WIN32) || defined(_WINDOWS)
	int i;
	int result;
	LineList ll;
	LinePtr lp;

	ARGSUSED(gUnusedArg);
	for (i=1; i<argc; i++) {
		InitLineList(&ll);
		result = FTPLocalGlob(&gConn, &ll, argv[i], (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
		if (result < 0) {
			FTPPerror(&gConn, result, kErrGlobFailed, "local glob", argv[i]);
		} else {
			for (lp = ll.first; lp != NULL; lp = lp->next) {
				if (lp->line != NULL) {
					if (rmdir(lp->line) < 0)
						perror(lp->line);
				}
			}
		}
		DisposeLineListContents(&ll);
	}
#else
	ARGSUSED(gUnusedArg);
	Sys(argc, argv, aip, "/bin/rmdir", 1);
#endif
}	/* LocalRmdirCmd */




/* Displays the current local working directory. */
void
LocalPwdCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	ARGSUSED(gUnusedArg);
	if (FTPGetLocalCWD(gLocalCWD, sizeof(gLocalCWD)) != NULL) {
		Trace(-1, "%s\n", gLocalCWD);
	}
}	/* LocalPwdCmd */




/* This is a simple interface to name service.  I prefer using this instead
 * of nslookup.
 */
void
LookupCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int i, j;
	struct hostent *hp;
	const char *host;
	char **cpp;
	struct in_addr ip_address;
	int shortMode, opt;
	char ipStr[16];

	ARGSUSED(gUnusedArg);
	shortMode = 1;

	GetoptReset();
	while ((opt = Getopt(argc, argv, "v")) >= 0) {
		if (opt == 'v')
			shortMode = 0;
		else {
			PrintCmdUsage(cmdp);
			return;
		}
	}

	for (i=gOptInd; i<argc; i++) {
		hp = GetHostEntry((host = argv[i]), &ip_address);
		if ((i > gOptInd) && (shortMode == 0))
			Trace(-1, "\n");
		if (hp == NULL) {
			Trace(-1, "Unable to get information about site %s.\n", host);
		} else if (shortMode) {
			MyInetAddr(ipStr, sizeof(ipStr), hp->h_addr_list, 0);
			Trace(-1, "%-40s %s\n", hp->h_name, ipStr);
		} else {
			Trace(-1, "%s:\n", host);
			Trace(-1, "    Name:     %s\n", hp->h_name);
			for (cpp = hp->h_aliases; *cpp != NULL; cpp++)
				Trace(-1, "    Alias:    %s\n", *cpp);
			for (j = 0, cpp = hp->h_addr_list; *cpp != NULL; cpp++, ++j) {
				MyInetAddr(ipStr, sizeof(ipStr), hp->h_addr_list, j);
				Trace(-1, "    Address:  %s\n", ipStr);
			}
		}
	}
}	/* LookupCmd */



/* Directory listing in a machine-readable format;
 * Mostly for debugging, since NcFTP uses MLSD automatically when it needs to.
 */
void
MlsCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int i;
	int opt;
	LinePtr linePtr, nextLinePtr;
	int result;
	LineList dirContents;
	int mlsd = 1, x;
	const char *item;

	ARGSUSED(gUnusedArg);
	GetoptReset();
	while ((opt = Getopt(argc, argv, "dt")) >= 0) {
		if ((opt == 'd') || (opt == 't')) {
			/* Use MLST instead of MLSD,
			 * which is similar to using "ls -d" instead of "ls".
			 */
			mlsd = 0;
		} else {
			PrintCmdUsage(cmdp);
			return;
		}
	}

	i = gOptInd;
	if (i == argc) {
		/* No args, do current directory. */
		x = 1;
		item = "";
		if ((result = FTPListToMemory2(&gConn, item, &dirContents, (mlsd == 0) ? "-d" : "", 1, &x)) < 0) {
			if (mlsd != 0) {
				FTPPerror(&gConn, result, 0, "Could not MLSD", item);
			} else {
				FTPPerror(&gConn, result, 0, "Could not MLST", item);
			}
		} else {
			for (linePtr = dirContents.first;
				linePtr != NULL;
				linePtr = nextLinePtr)
			{
				nextLinePtr = linePtr->next;
				(void) fprintf(stdout, "%s\n", linePtr->line);
				Trace(0, "%s\n", linePtr->line);
			}
		}
	}

	for ( ; i<argc; i++) {
		x = 1;
		item = argv[i];
		if ((result = FTPListToMemory2(&gConn, item, &dirContents, (mlsd == 0) ? "-d" : "", 1, &x)) < 0) {
			if (mlsd != 0) {
				FTPPerror(&gConn, result, 0, "Could not MLSD", item);
			} else {
				FTPPerror(&gConn, result, 0, "Could not MLST", item);
			}
		} else {
			for (linePtr = dirContents.first;
				linePtr != NULL;
				linePtr = nextLinePtr)
			{
				nextLinePtr = linePtr->next;
				(void) fprintf(stdout, "%s\n", linePtr->line);
				Trace(0, "%s\n", linePtr->line);
			}
		}
	}
}	/* MlsCmd */




/* Create directories on the remote system. */
void
MkdirCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int i;
	int opt;
	int result;
	int recurseFlag = kRecursiveNo;

	ARGSUSED(gUnusedArg);
	GetoptReset();
	while ((opt = Getopt(argc, argv, "p")) >= 0) {
		if (opt == 'p') {
			/* Try creating intermediate directories if they
			 * don't exist.
			 *
			 * For example if only /pub/stuff existed, and you
			 * do a "mkdir -p /pub/stuff/a/b/c", the "a" and "b"
			 * directories would also be created.
			 */
			recurseFlag = kRecursiveYes;
		} else {
			PrintCmdUsage(cmdp);
			return;
		}
	}

	for (i=gOptInd; i<argc; i++) {
		result = FTPMkdir(&gConn, argv[i], recurseFlag);
		if (result < 0)
			FTPPerror(&gConn, result, kErrMKDFailed, "Could not mkdir", argv[i]);
	}

	/* Really should just flush only the modified directories... */
	FlushLsCache();
}	/* MkdirCmd */



/*VARARGS*/
static void
OpenMsg(const char *const fmt, ...)
{
	va_list ap;
	char buf[512];
	size_t len, padlim;

	padlim = (size_t) gScreenColumns;
	if ((size_t) gScreenColumns > (sizeof(buf) - 1))
		padlim = sizeof(buf) - 1;

	va_start(ap, fmt);
#ifdef HAVE_VSNPRINTF
	len = (size_t) vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
	va_end(ap);
	buf[sizeof(buf) - 1] = '\0';
#else
	(void) vsprintf(buf, fmt, ap);
	va_end(ap);
	len = strlen(buf);
#endif
	buf[len] = '\0';
	Trace(9, "%s\n", buf);
	for (; len < padlim; len++)
		buf[len] = ' ';
	buf[len] = '\0';

#if 0
	(void) fprintf(stdout, "\r%s", buf);
#else
	(void) fprintf(stdout, "%s\r", buf);
#endif
	(void) fflush(stdout);
}	/* OpenMsg */




static void
NcFTPOpenPrintResponseProc(const FTPCIPtr cipUNUSED, ResponsePtr rp)
{
	gUnusedArg = (cipUNUSED != NULL);
	if ((rp->printMode & kResponseNoPrint) != 0)
		return;
#if 0
	if (rp->code == 331)	/* Skip: "331 User name okay, need password." */
		return;
#else
	/* This is only used to print errors. */
	if (rp->code < 400)
		return;
#endif
	PrintResp(&rp->msg);
}	/* NcFTPOpenPrintResponseProc */



static void
NcFTPOnConnectMessageProc(const FTPCIPtr cipUNUSED, ResponsePtr rp)
{
	gUnusedArg = (cipUNUSED != NULL);
	(void) printf("\n");
	PrintResp(&rp->msg);
	OpenMsg("Logging in...");
}	/* NcFTPOnConnectMessageProc */



static void
NcFTPOnLoginMessageProc(const FTPCIPtr cipUNUSED, ResponsePtr rp)
{
	gUnusedArg = (cipUNUSED != NULL);
	(void) printf("\n");
	PrintResp(&rp->msg);
	OpenMsg("Logging in...");
}	/* NcFTPOnLoginMessageProc */




static void
NcFTPRedialStatusProc(const FTPCIPtr cipUNUSED, int mode, int val)
{
	gUnusedArg = (cipUNUSED != NULL);
	if (mode == kRedialStatusDialing) {
		if (val > 0) {
			OpenMsg("Redialing (try %d)...", val);
			sleep(1);	/* Give time for message to stay */
		}
	} else if (mode == kRedialStatusSleeping) {
		OpenMsg("Sleeping %d seconds...", val);
	}
}	/* NcFTPRedialStatusProc */




static void
NcFTPGetPassphraseProc(const FTPCIPtr cip, LineListPtr pwPrompt, char *pass, size_t dsize)
{
	LinePtr lp;

	(void) printf("\nPassword requested by %s for user \"%s\".\n\n",
		cip->host,
		cip->user
		);

	for (lp = pwPrompt->first; lp != NULL; lp = lp->next) {
		(void) printf("    %s\n", lp->line);
	}
	(void) printf("\n");
	(void) gl_getpass("Password: ", pass, (int) dsize);
}	/* NcFTPGetPassphraseProc */




/* Attempts to establish a new FTP connection to a remote host. */
int
DoOpen(void)
{
	int result;
	char ipstr[128];
	char ohost[128];
#ifdef SIGALRM
	sigproc_t osigalrm;
#endif
	char prompt[256];

	if (gConn.firewallType == kFirewallNotInUse) {
		(void) STRNCPY(ohost, gConn.host);
		OpenMsg("Resolving %s...", ohost);
		if ((gLoadedBm != 0) && (gBm.lastIP[0] != '\0')) {
			result = GetHostByName(ipstr, sizeof(ipstr), ohost, 3);
			if (result < 0) {
				(void) STRNCPY(ipstr, gBm.lastIP);
				result = 0;
			} else {
				result = GetHostByName(ipstr, sizeof(ipstr), ohost, 7);
			}
		} else {
			result = GetHostByName(ipstr, sizeof(ipstr), ohost, 10);
		}
		if (result < 0) {
			(void) printf("\n");
			(void) printf("Unknown host \"%s\".\n", ohost);
			return (-1);
		}
		(void) STRNCPY(gConn.host, ipstr);
		OpenMsg("Connecting to %s...", ipstr);
	} else {
		OpenMsg("Connecting to %s via %s...", gConn.host, gConn.firewallHost);
		Trace(0, "Fw: %s  Type: %d  User: %s  Pass: %s  Port: %u\n",
			gConn.firewallHost,
			gConn.firewallType,
			gConn.firewallUser,
			(gConn.firewallPass[0] == '\0') ? "(none)" : "********",
			gConn.firewallPort
		);
		Trace(0, "FwExceptions: %s\n", gFirewallExceptionList);
		if (strchr(gLib.ourHostName, '.') == NULL) {
			Trace(0, "NOTE:  Your domain name could not be detected.\n");
			if (gConn.firewallType != kFirewallNotInUse) {
				Trace(0, "       Make sure you manually add your domain name to firewall-exception-list.\n");
			}
		}
	}

	if (gConn.firewallPass[0] == '\0') {
		switch (gConn.firewallType) {
			case kFirewallNotInUse:
				break;
			case kFirewallUserAtSite:
				break;
			case kFirewallLoginThenUserAtSite:
			case kFirewallSiteSite:
			case kFirewallOpenSite:
			case kFirewallUserAtUserPassAtPass:
			case kFirewallFwuAtSiteFwpUserPass:
			case kFirewallUserAtSiteFwuPassFwp:
				(void) printf("\n");
				(void) STRNCPY(prompt, "Password for firewall user \"");
				(void) STRNCAT(prompt, gConn.firewallUser);
				(void) STRNCAT(prompt, "\" at ");
				(void) STRNCAT(prompt, gConn.firewallHost);
				(void) STRNCAT(prompt, ": ");
				(void) gl_getpass(prompt, gConn.firewallPass, sizeof(gConn.firewallPass));
				break;
		}
	}

	if ((gConn.user[0] != '\0') && (strcmp(gConn.user, "anonymous") != 0) && (strcmp(gConn.user, "ftp") != 0)) {
		gConn.passphraseProc = NcFTPGetPassphraseProc;
	}

	/* Register our callbacks. */
	gConn.printResponseProc = NcFTPOpenPrintResponseProc;
	gConn.onConnectMsgProc = NcFTPOnConnectMessageProc;
	gConn.onLoginMsgProc = NcFTPOnLoginMessageProc;
	gConn.redialStatusProc = NcFTPRedialStatusProc;

#ifdef SIGALRM
	osigalrm = NcSignal(SIGALRM, (FTPSigProc) SIG_IGN);
	result = FTPOpenHost(&gConn);
	(void) NcSignal(SIGALRM, osigalrm);
#else
	result = FTPOpenHost(&gConn);
#endif

	if (gConn.firewallType == kFirewallNotInUse)
		(void) STRNCPY(gConn.host, ohost);		/* Put it back. */
	if (result >= 0) {
		(void) time(&gBm.lastCall);
		LogOpen(gConn.host);
		OpenMsg("Logged in to %s.", gConn.host);
		(void) printf("\n");

		/* Remove callback. */
		gConn.printResponseProc = 0;

		/* Need to note what our "root" was before we change it. */
		if (gConn.startingWorkingDirectory == NULL) {
			(void) STRNCPY(gRemoteCWD, "/");	/* Guess! */
		} else {
			(void) STRNCPY(gRemoteCWD, gConn.startingWorkingDirectory);
		}
		(void) STRNCPY(gStartDir, gRemoteCWD);

		/* If the bookmark specified a remote directory, change to it now. */
		if ((gLoadedBm != 0) && (gBm.dir[0] != '\0')) {
			result = Chdirs(&gConn, gBm.dir);
			if (result < 0) {
				FTPPerror(&gConn, result, kErrCWDFailed, "Could not chdir to previous directory", gBm.dir);
			}
			Trace(-1, "Current remote directory is %s.\n", gRemoteCWD);
		}

		/* If the bookmark specified a local directory, change to it now. */
		if ((gLoadedBm != 0) && (gBm.ldir[0] != '\0')) {
			(void) chdir(gBm.ldir);
			(void) STRNCPY(gPrevLocalCWD, gLocalCWD);
			if (FTPGetLocalCWD(gLocalCWD, sizeof(gLocalCWD)) != NULL) {
				Trace(-1, "Current local directory is %s.\n", gLocalCWD);
			}
		}

		/* Identify the FTP client type to the server.  Most don't understand this yet. */
		if (gConn.hasCLNT != kCommandNotAvailable)
			(void) FTPCmd(&gConn, "CLNT NcFTP %.5s %s", gVersion + 11, gOS);
		return (0);
	} else {
		FTPPerror(&gConn, result, 0, "Could not open host", gConn.host);
	}

	/* Remove callback. */
	gConn.printResponseProc = 0;
	(void) printf("\n");
	return (-1);
}	/* Open */




/* Chooses a new remote system to connect to, and attempts to establish
 * a new FTP connection.  This function is in charge of collecting the
 * information needed to do the open, and then doing it.
 */
void
OpenCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int c;
	int opts = 0;
	int uOptInd = 0;
	int n;
	int rc;
	char url[256];
	char urlfile[128];
	int directoryURL = 0;
	LineList cdlist;
	LinePtr lp;
	char prompt[256];

	ARGSUSED(gUnusedArg);
	FlushLsCache();
	CloseHost();
	gLoadedBm = 0;
	InitConnectionInfo();

	/* Need to find the host argument first. */
	GetoptReset();
	while ((c = Getopt(argc, argv, "aP:u:p:J:rd:g:")) > 0) switch(c) {
		case 'u':
			uOptInd = gOptInd + 1;
			opts++;
			break;
		default:
			opts++;
	}
	if (gOptInd < argc) {
		(void) STRNCPY(gConn.host, argv[gOptInd]);
		(void) STRNCPY(url, argv[gOptInd]);
	} else if (uOptInd > argc) {
		/* Special hack for v2.4.2 compatibility */
		(void) STRNCPY(gConn.host, argv[argc - 1]);
		(void) STRNCPY(url, argv[argc - 1]);
	} else {
		/* No host arg */
		if (opts > 0) {
			PrintCmdUsage(cmdp);
		} else if (RunBookmarkEditor(gConn.host, sizeof(gConn.host)) == 0) {
			if (gConn.host[0] != '\0') {
				gLoadedBm = 1;
				/* okay, now fall through */
			} else {
				return;
			}
		} else if (PrintHosts() <= 0) {
			(void) printf("You haven't bookmarked any FTP sites.\n");
			(void) printf("Before closing a site, you can use the \"bookmark\" command to save the current\nhost and directory for next time.\n");
			return;
		} else {
			(void) printf("\nTo use a bookmark, use the \"open\" command with the name of the bookmark.\n");
			return;
		}
	}

	InitLineList(&cdlist);

	if (GetBookmark(gConn.host, &gBm) >= 0) {
		gLoadedBm = 1;
		(void) STRNCPY(gConn.host, gBm.name);
		(void) STRNCPY(gConn.user, gBm.user);
		(void) STRNCPY(gConn.pass, gBm.pass);
		(void) STRNCPY(gConn.acct, gBm.acct);
		gConn.hasSIZE = gBm.hasSIZE;
		gConn.hasMDTM = gBm.hasMDTM;
		gConn.hasUTIME = gBm.hasUTIME;
		gConn.port = gBm.port;

		/* Note:  Version 3 only goes off of the
		 * global "gDataPortMode" setting instead of
		 * setting the dataPortMode on a per-site
		 * basis.
		 */
		gConn.hasPASV = gBm.hasPASV;
	} else {
		SetBookmarkDefaults(&gBm);

		memcpy(&gTmpURLConn, &gConn, sizeof(gTmpURLConn));
		rc = DecodeDirectoryURL(&gTmpURLConn, url, &cdlist, urlfile, sizeof(urlfile));
		if (rc == kMalformedURL) {
			(void) fprintf(stdout, "Malformed URL: %s\n", url);
			DisposeLineListContents(&cdlist);
			return;
		} else if (rc == kNotURL) {
			directoryURL = 0;
		} else {
			/* It was a URL. */
			if (urlfile[0] != '\0') {
				/* It was obviously not a directory */
				(void) fprintf(stdout, "Use ncftpget or ncftpput to handle file URLs.\n");
				DisposeLineListContents(&cdlist);
				return;
			}
			memcpy(&gConn, &gTmpURLConn, sizeof(gConn));
			directoryURL = 1;
		}
	}

	if (MayUseFirewall(gConn.host, gFirewallType, gFirewallExceptionList) != 0) {
		gConn.firewallType = gFirewallType;
		(void) STRNCPY(gConn.firewallHost, gFirewallHost);
		(void) STRNCPY(gConn.firewallUser, gFirewallUser);
		(void) STRNCPY(gConn.firewallPass, gFirewallPass);
		gConn.firewallPort = gFirewallPort;
	}

	GetoptReset();
	while ((c = Getopt(argc, argv, "aP:u:p:J:j:rd:g:")) > 0) switch(c) {
		case 'J':
		case 'j':
			(void) STRNCPY(gConn.acct, gOptArg);
			break;
		case 'a':
			(void) STRNCPY(gConn.user, "anonymous");
			(void) STRNCPY(gConn.pass, "");
			(void) STRNCPY(gConn.acct, "");
			break;
		case 'P':
			gConn.port = atoi(gOptArg);
			break;
		case 'u':
			if (uOptInd <= argc)
				(void) STRNCPY(gConn.user, gOptArg);
			break;
		case 'p':
			(void) STRNCPY(gConn.pass, gOptArg);	/* Don't recommend doing this! */
			break;
		case 'r':
			/* redial is on by default */
			break;
		case 'g':
			n = atoi(gOptArg);
			gConn.maxDials = n;
			break;
		case 'd':
			n = atoi(gOptArg);
			if (n >= 10)
				gConn.redialDelay = n;
			break;
		default:
			PrintCmdUsage(cmdp);
			DisposeLineListContents(&cdlist);
			return;
	}

	if (uOptInd > argc) {
		(void) STRNCPY(prompt, "Username at ");
		(void) STRNCAT(prompt, gConn.host);
		(void) STRNCAT(prompt, ": ");
		(void) gl_getpass(prompt, gConn.user, sizeof(gConn.user));
	}

	rc = DoOpen();
	if ((rc >= 0) && (directoryURL != 0)) {
		for (lp = cdlist.first; lp != NULL; lp = lp->next) {
			rc = FTPChdir(&gConn, lp->line);
			if (rc != kNoErr) {
				FTPPerror(&gConn, rc, kErrCWDFailed, "Could not chdir to", lp->line);
				break;
			}
		}
		rc = FTPGetCWD(&gConn, gRemoteCWD, sizeof(gRemoteCWD));
		if (rc != kNoErr) {
			FTPPerror(&gConn, rc, kErrPWDFailed, NULL, NULL);
		} else {
			(void) printf("Current remote directory is %s.\n", gRemoteCWD);
		}
	}
	DisposeLineListContents(&cdlist);
}	/* OpenCmd */




/* View a remote file through the users $PAGER. */
void
PageCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int result;
	int i;
	FILE *volatile stream;
#if defined(WIN32) || defined(_WINDOWS)
#else
	int sj;
	vsigproc_t osigpipe, osigint;
#endif

	ARGSUSED(gUnusedArg);
	stream = OpenPager();
	if (stream == NULL) {
		return;
	}

#if defined(WIN32) || defined(_WINDOWS)
#else

#ifdef HAVE_SIGSETJMP
	osigpipe = osigint = (sigproc_t) 0;
	sj = sigsetjmp(gCancelJmp, 1);
#else	/* HAVE_SIGSETJMP */
	osigpipe = osigint = (sigproc_t) 0;
	sj = setjmp(gCancelJmp);
#endif	/* HAVE_SIGSETJMP */

	if (sj != 0) {
		/* Caught a signal. */
		(void) NcSignal(SIGPIPE, (FTPSigProc) SIG_IGN);
		ClosePager(stream);
		(void) NcSignal(SIGPIPE, osigpipe);
		(void) NcSignal(SIGINT, osigint);
		(void) fprintf(stderr, "Canceled.\n");
		Trace(0, "Canceled because of signal %d.\n", gGotSig);
		gMayCancelJmp = 0;
		return;
	} else {
		osigpipe = NcSignal(SIGPIPE, Cancel);
		osigint = NcSignal(SIGINT, Cancel);
		gMayCancelJmp = 1;
	}

#endif

	for (i=1; i<argc; i++) {
		result = FTPGetOneFile2(&gConn, argv[i], NULL, kTypeAscii, _fileno(stream), kResumeNo, kAppendNo);
		if (result < 0) {
			if (errno != EPIPE) {
				ClosePager(stream);
				stream = NULL;
				FTPPerror(&gConn, result, kErrCouldNotStartDataTransfer, argv[0], argv[i]);
			}
			break;
		}
	}

#if defined(WIN32) || defined(_WINDOWS)
	ClosePager(stream);
#else
	(void) NcSignal(SIGPIPE, (FTPSigProc) SIG_IGN);
	ClosePager(stream);
	(void) NcSignal(SIGPIPE, osigpipe);
	(void) NcSignal(SIGINT, osigint);
#endif
	gMayCancelJmp = 0;
}	/* PageCmd */




static int
NcFTPConfirmResumeUploadProc(
	const char *volatile localpath,
	volatile longest_int localsize,
	volatile time_t localmtime,
	const char *volatile *remotepath,
	volatile longest_int remotesize,
	volatile time_t remotemtime,
	volatile longest_int *volatile startPoint)
{
	int zaction = kConfirmResumeProcSaidBestGuess;
	char tstr[80], ans[32];
	static char newname[128];	/* arrggh... static. */

	if (gResumeAnswerAll != kConfirmResumeProcNotUsed)
		return (gResumeAnswerAll);

	if (gAutoResume != 0)
		return (kConfirmResumeProcSaidBestGuess);

	printf("\nThe remote file \"%s\" already exists.\n", *remotepath);

	if ((localmtime != kModTimeUnknown) && (localsize != kSizeUnknown)) {
		(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &localmtime));
		(void) printf(
#if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
			"\tLocal:  %12lld bytes, dated %s.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
			"\tLocal:  %12qd bytes, dated %s.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
			"\tLocal:  %12I64d bytes, dated %s.\n",
#else
			"\tLocal:  %12ld bytes, dated %s.\n",
#endif
			localsize,
			tstr
		);
		if ((remotemtime == localmtime) && (remotesize == localsize)) {
			(void) printf("\t(Files are identical, skipped)\n\n");
			return (kConfirmResumeProcSaidSkip);
		}
	} else if (localsize != kSizeUnknown) {
		(void) printf(
#if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
			"\tLocal:  %12lld bytes, date unknown.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
			"\tLocal:  %12qd bytes, date unknown.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
			"\tLocal:  %12I64d bytes, date unknown.\n",
#else
			"\tLocal:  %12ld bytes, date unknown.\n",
#endif
			localsize
		);
	} else if (localmtime != kModTimeUnknown) {
		(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &localmtime));
		(void) printf(
			"\tLocal:  size unknown, dated %s.\n",
			tstr
		);
	}

	tstr[sizeof(tstr) - 1] = '\0';
	if ((remotemtime != kModTimeUnknown) && (remotesize != kSizeUnknown)) {
		(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &remotemtime));
		(void) printf(
#if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
			"\tRemote: %12lld bytes, dated %s.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
			"\tRemote: %12qd bytes, dated %s.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
			"\tRemote: %12I64d bytes, dated %s.\n",
#else
			"\tRemote: %12ld bytes, dated %s.\n",
#endif
			remotesize,
			tstr
		);
	} else if (remotesize != kSizeUnknown) {
		(void) printf(
#if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
			"\tRemote: %12lld bytes, date unknown.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
			"\tRemote: %12qd bytes, date unknown.\n",
#elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
			"\tRemote: %12I64d bytes, date unknown.\n",
#else
			"\tRemote: %12ld bytes, date unknown.\n",
#endif
			remotesize
		);
	} else if (remotemtime != kModTimeUnknown) {
		(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &remotemtime));
		(void) printf(
			"\tRemote: size unknown, dated %s.\n",
			tstr
		);
	}

	printf("\n");
	fflush(stdin);
	(void) memset(ans, 0, sizeof(ans));
	for (;;) {
		(void) printf("\t[O]verwrite?");
		if ((gConn.hasREST == kCommandAvailable) && (remotesize < localsize))
			printf("  [R]esume?");
		printf("  [A]ppend to?  [S]kip?  [N]ew Name?\n");
		(void) printf("\t[O!]verwrite all?");
		if ((gConn.hasREST == kCommandAvailable) && (remotesize < localsize))
			printf("  [R!]esume all?");
		printf("  [S!]kip all?  [C]ancel  > ");
		(void) fgets(ans, sizeof(ans) - 1, stdin);
		switch ((int) ans[0]) {
			case 'c':
			case 'C':
				ans[0] = 'C';
				ans[1] = '\0';
				zaction = kConfirmResumeProcSaidCancel;
				break;
			case 'o':
			case 'O':
				ans[0] = 'O';
				zaction = kConfirmResumeProcSaidOverwrite;
				break;
			case 'r':
			case 'R':
				if (gConn.hasREST != kCommandAvailable) {
					printf("\tResume is not available on this server.\n\n");
					ans[0] = '\0';
					break;
				} else if (remotesize > localsize) {
					printf("\tCannot resume when remote file is already larger than the local file.\n\n");
					ans[0] = '\0';
					break;
				} else if (remotesize == localsize) {
					printf("\tRemote file is already the same size as the local file.\n\n");
					ans[0] = '\0';
					break;
				}
				ans[0] = 'R';
				*startPoint = remotesize;
				zaction = kConfirmResumeProcSaidResume;
				if (OneTimeMessage("auto-resume") != 0) {
					printf("\n\tNOTE: If you want NcFTP to guess automatically, \"set auto-resume yes\"\n\n");
				}
				break;
			case 's':
			case 'S':
				ans[0] = 'S';
				zaction = kConfirmResumeProcSaidSkip;
				break;
			case 'n':
			case 'N':
				ans[0] = 'N';
				ans[1] = '\0';
				zaction = kConfirmResumeProcSaidOverwrite;
				break;
			case 'a':
			case 'A':
				ans[0] = 'A';
				ans[1] = '\0';
				zaction = kConfirmResumeProcSaidAppend;
				break;
			case 'g':
			case 'G':
				ans[0] = 'G';
				zaction = kConfirmResumeProcSaidBestGuess;
				break;
			default:
				ans[0] = '\0';
		}
		if (ans[0] != '\0')
			break;
	}

	if (ans[0] == 'N') {
		(void) memset(newname, 0, sizeof(newname));
		printf("\tSave as:  ");
		fflush(stdin);
		(void) fgets(newname, sizeof(newname) - 1, stdin);
		newname[strlen(newname) - 1] = '\0';
		if (newname[0] == '\0') {
			/* Nevermind. */
			printf("Skipped %s.\n", localpath);
			zaction = kConfirmResumeProcSaidSkip;
		} else {
			*remotepath = newname;
		}
	}

	if (ans[1] == '!')
		gResumeAnswerAll = zaction;
	return (zaction);
}	/* NcFTPConfirmResumeUploadProc */




/* Upload files to the remote system, permissions permitting. */
void
PutCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int opt;
	int renameMode = 0;
	int recurseFlag = kRecursiveNo;
	int appendFlag = kAppendNo;
	const char *dstdir = NULL;
	int rc;
	int i;
	int doGlob;
	int xtype = gBm.xferType;
	int nD = 0;
	int deleteFlag = kDeleteNo;
	int resumeFlag = kResumeYes;
	char pattern[256];
	vsigproc_t osigint;
	ConfirmResumeUploadProc confirmProc;

	confirmProc = NcFTPConfirmResumeUploadProc;
	gResumeAnswerAll = kConfirmResumeProcNotUsed;	/* Ask at least once each time */
	ARGSUSED(gUnusedArg);
	GetoptReset();
	while ((opt = Getopt(argc, argv, "AafZzrRD")) >= 0) switch (opt) {
		case 'a':
			xtype = kTypeAscii;
			break;
		case 'A':
			/* Append to remote files, instead of truncating
			 * them first.
			 */
			appendFlag = kAppendYes;
			break;
		case 'f':
		case 'Z':
			/* Do not try to resume a download, even if it
			 * appeared that some of the file was transferred
			 * already.
			 */
			resumeFlag = kResumeNo;
			confirmProc = NoConfirmResumeUploadProc;
			break;
		case 'z':
			/* Special flag that lets you specify the
			 * destination file.  Normally a "put" will
			 * write the file by the same name as the
			 * local file's basename.
			 */
			renameMode = 1;
			break;
		case 'r':
		case 'R':
			recurseFlag = kRecursiveYes;
			/* If the item is a directory, get the
			 * directory and all its contents.
			 */
			recurseFlag = kRecursiveYes;
			break;
		case 'D':
			/* You can delete the local file after
			 * you uploaded it successfully by using
			 * the -DD option.  It requires two -D's
			 * to minimize the odds of accidentally
			 * using a single -D.
			 */
			nD++;
			break;
		default:
			PrintCmdUsage(cmdp);
			return;
	}

	if (nD >= 2)
		deleteFlag = kDeleteYes;

	if (renameMode != 0) {
		if (gOptInd > (argc - 2)) {
			PrintCmdUsage(cmdp);
			(void) fprintf(stderr, "\nFor put with rename, try \"put -z local-path-name remote-path-name\".\n");
			return;
		}
		osigint = NcSignal(SIGINT, XferCanceller);
		rc = FTPPutOneFile3(&gConn, argv[gOptInd], argv[gOptInd + 1], xtype, (-1), appendFlag, NULL, NULL, resumeFlag, deleteFlag, confirmProc, 0);
		if (rc < 0)
			FTPPerror(&gConn, rc, kErrCouldNotStartDataTransfer, "put", argv[gOptInd + 1]);
	} else {
		osigint = NcSignal(SIGINT, XferCanceller);
		for (i=gOptInd; i<argc; i++) {
			doGlob = (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes;
			STRNCPY(pattern, argv[i]);
			StrRemoveTrailingSlashes(pattern);
			rc = FTPPutFiles3(&gConn, pattern, dstdir, recurseFlag, doGlob, xtype, appendFlag, NULL, NULL, resumeFlag, deleteFlag, confirmProc, 0);
			if (rc < 0)
				FTPPerror(&gConn, rc, kErrCouldNotStartDataTransfer, "put", argv[i]);
		}
	}

	/* Really should just flush only the modified directories... */
	FlushLsCache();

	(void) NcSignal(SIGINT, osigint);
	(void) fflush(stdin);
}	/* PutCmd */




/* Displays the current remote working directory path. */
void
PwdCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int result;
	char url[256];
	char olddir[256];

	ARGSUSED(gUnusedArg);
#ifdef USE_WHAT_SERVER_SAYS_IS_CWD
	result = FTPGetCWD(&gConn, gRemoteCWD, sizeof(gRemoteCWD));
	CurrentURL(url, sizeof(url), 0);
	if (result < 0) {
		FTPPerror(&gConn, result, kErrPWDFailed, "Could not get remote working directory", NULL);
	} else {
		Trace(-1, "%s\n", url);
	}
#else /* USE_CLIENT_SIDE_CALCULATED_CWD */

	/* Display the current working directory, as
	 * maintained by us.
	 */
	CurrentURL(url, sizeof(url), 0);
	Trace(-1, "  %s\n", url);
	olddir[sizeof(olddir) - 2] = '\0';
	STRNCPY(olddir, gRemoteCWD);

	/* Now see what the server reports as the CWD.
	 * Because of symlinks, it could be different
	 * from what we are using.
	 */
	result = FTPGetCWD(&gConn, gRemoteCWD, sizeof(gRemoteCWD));
	if ((result == kNoErr) && (strcmp(gRemoteCWD, olddir) != 0)) {
		Trace(-1, "This URL is also valid on this server:\n");
		CurrentURL(url, sizeof(url), 0);
		Trace(-1, "  %s\n", url);
		if (olddir[sizeof(olddir) - 2] == '\0') {
			/* Go back to using the non-resolved version. */
			STRNCPY(gRemoteCWD, olddir);
		}
	}
#endif
}	/* PwdCmd */




/* Sets a flag so that the command shell exits. */
void
QuitCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	ARGSUSED(gUnusedArg);
	gDoneApplication = 1;
}	/* QuitCmd */




/* Send a command string to the FTP server, verbatim. */
void
QuoteCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	char cmdbuf[256];
	int i;

	ARGSUSED(gUnusedArg);
	(void) STRNCPY(cmdbuf, argv[1]);
	for (i=2; i<argc; i++) {
		(void) STRNCAT(cmdbuf, " ");
		(void) STRNCAT(cmdbuf, argv[i]);
	}
	(void) FTPCmd(&gConn, "%s", cmdbuf);
	PrintResp(&gConn.lastFTPCmdResultLL);
}	/* QuoteCmd */




/* Expands a remote regex.  Mostly a debugging command. */
void
RGlobCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int i;
	int result;
	int np = 0;
	LineList ll;
	LinePtr lp;

	ARGSUSED(gUnusedArg);
	for (i=1; i<argc; i++) {
		InitLineList(&ll);
		result = FTPRemoteGlob(&gConn, &ll, argv[i], (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
		if (result < 0) {
			FTPPerror(&gConn, result, kErrGlobFailed, "remote glob", argv[i]);
		} else {
			for (lp = ll.first; lp != NULL; lp = lp->next) {
				if (lp->line != NULL) {
					if (np > 0)
						(void) printf(" ");
					(void) printf("%s", lp->line);
					np++;
				}
			}
		}
		DisposeLineListContents(&ll);
	}
	(void) printf("\n");
}	/* RGlobCmd */




/* Renames a remote file. */
void
RenameCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int result;

	ARGSUSED(gUnusedArg);
	result = FTPRename(&gConn, argv[1], argv[2]);
	if (result < 0)
		FTPPerror(&gConn, result, kErrRenameFailed, "rename", argv[1]);
	else {
		/* Really should just flush only the modified directories... */
		FlushLsCache();
	}
}	/* RenameCmd */




/* Removes a directory on the remote host. */
void
RmdirCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int result;
	int i, c;
	int recursive = kRecursiveNo;

	ARGSUSED(gUnusedArg);
	GetoptReset();
	while ((c = Getopt(argc, argv, "rf")) > 0) switch(c) {
		case 'r':
			recursive = kRecursiveYes;
			break;
		case 'f':
			/* ignore */
			break;
		default:
			PrintCmdUsage(cmdp);
			return;
	}
	for (i=gOptInd; i<argc; i++) {
		result = FTPRmdir(&gConn, argv[i], recursive, (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
		if (result < 0)
			FTPPerror(&gConn, result, kErrRMDFailed, "rmdir", argv[i]);
	}

	/* Really should just flush only the modified directories... */
	FlushLsCache();
}	/* RmdirCmd */




/* Asks the remote server for help on how to use its server. */
void
RmtHelpCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int i, result;
	LineList ll;
	LinePtr lp;

	ARGSUSED(gUnusedArg);
	if (argc == 1) {
		result = FTPRemoteHelp(&gConn, NULL, &ll);
		if (result < 0)
			FTPPerror(&gConn, result, kErrHELPFailed, "HELP failed", NULL);
		else {
			for (lp = ll.first; lp != NULL; lp = lp->next)
				(void) printf("%s\n", lp->line);
		}
		DisposeLineListContents(&ll);
	} else {
		for (i=1; i<argc; i++) {
			result = FTPRemoteHelp(&gConn, argv[i], &ll);
			if (result < 0)
				FTPPerror(&gConn, result, kErrHELPFailed, "HELP failed for", argv[i]);
			else {
				for (lp = ll.first; lp != NULL; lp = lp->next)
					(void) printf("%s\n", lp->line);
			}
			DisposeLineListContents(&ll);
		}
	}
}	/* RmtHelpCmd */




/* Show and/or change customizable program settings. These changes are saved
 * at the end of the program's run.
 */
void
SetCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	ARGSUSED(gUnusedArg);
	if (argc < 2)
		Set(NULL, NULL);
	else if (argc == 2)
		Set(argv[1], NULL);
	else
		Set(argv[1], argv[2]);
}	/* SetCmd */




/* Local shell escape. */
void
ShellCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
#if defined(WIN32) || defined(_WINDOWS)
#else
	const char *cp;
	pid_t pid;
	int status;
	vsigproc_t osigint;

	osigint = NcSignal(SIGINT, (FTPSigProc) SIG_IGN);
	ARGSUSED(gUnusedArg);
	pid = fork();
	if (pid < (pid_t) 0) {
		perror("fork");
	} else if (pid == 0) {
		cp = strrchr(gShell, '/');
		if (cp == NULL)
			cp = gShell;	/* bug */
		else
			cp++;
		if (argc == 1) {
			execl(gShell, cp, NULL);
			perror(gShell);
			exit(1);
		} else {
			execvp(argv[1], (char **) argv + 1);
			perror(gShell);
			exit(1);
		}
	} else {
		/* parent */
		for (;;) {
#ifdef HAVE_WAITPID
			if ((waitpid(pid, &status, 0) < 0) && (errno != EINTR))
				break;
#else
			if ((wait(&status) < 0) && (errno != EINTR))
				break;
#endif
			if (WIFEXITED(status) || WIFSIGNALED(status))
				break;		/* done */
		}
	}
	(void) NcSignal(SIGINT, osigint);
#endif
}	/* ShellCmd */




/* Send a command string to the FTP server, verbatim. */
void
SiteCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	char cmdbuf[256];
	int i;

	ARGSUSED(gUnusedArg);
	(void) STRNCPY(cmdbuf, "SITE");
	for (i=1; i<argc; i++) {
		(void) STRNCAT(cmdbuf, " ");
		(void) STRNCAT(cmdbuf, argv[i]);
	}
	(void) FTPCmd(&gConn, "%s", cmdbuf);
	PrintResp(&gConn.lastFTPCmdResultLL);
}	/* SiteCmd */




static time_t
GetStartSpoolDate(const char *s)
{
	char *cp;
	char s2[64];
	time_t now, when;
	int toff, n, c, hr, min;
	struct tm lt, *ltp;

	STRNCPY(s2, s);
	cp = strchr(s2, ':');
	if ((s2[0] == 'n') || (s2[0] == '+')) {
		/* "now + XX hours" or
		 * "+ XX hours"
		 */
		cp = strchr(s2, '+');
		if (cp == NULL)
			return ((time_t) -1);
		++cp;
		toff = 0;
		n = 0;
		(void) sscanf(cp, "%d%n", &toff, &n);
		if ((n <= 0) || (toff <= 0))
			return ((time_t) -1);
		cp += n;
		while ((*cp != '\0') && (!isalpha(*cp)))
			cp++;
		c = *cp;
		if (isupper(c))
			c = tolower(c);
		if (c == 's') {
			/* seconds */
		} else if (c == 'm') {
			/* minutes */
			toff *= 60;
		} else if (c == 'h') {
			/* hours */
			toff *= 3600;
		} else if (c == 'd') {
			/* days */
			toff *= 86400;
		} else {
			/* unrecognized unit */
			return ((time_t) -1);
		}
		time(&now);
		when = now + (time_t) toff;
	} else if (cp != NULL) {
		/* HH:MM, as in "23:38" */
		time(&now);
		ltp = localtime(&now);
		if (ltp == NULL)
			return ((time_t) -1);	/* impossible */
		lt = *ltp;
		*cp = ' ';
		hr = -1;
		min = -1;
		(void) sscanf(s2, "%d%d", &hr, &min);
		if ((hr < 0) || (min < 0))
			return ((time_t) -1);
		lt.tm_hour = hr;
		lt.tm_min = min;
		when = mktime(&lt);
		if ((when == (time_t) -1) || (when == (time_t) 0))
			return (when);
		if (when < now)
			when += (time_t) 86400;
	} else {
		when = UnDate(s2);
	}
	return (when);
}	/* GetStartSpoolDate */



static int
SpoolCheck(void)
{
	if (CanSpool() < 0) {
#if defined(WIN32) || defined(_WINDOWS)
		(void) printf("Sorry, spooling isn't allowed until you run Setup.exe.\n");
#else
		(void) printf("Sorry, spooling isn't allowed because this user requires that the NCFTPDIR\nenvironment variable be set to a directory to write datafiles to.\n");
#endif
		return (-1);
	} else if (HaveSpool() == 0) {
#if defined(WIN32) || defined(_WINDOWS)
		(void) printf("Sorry, the \"ncftpbatch\" program could not be found.\nPlease re-run Setup to correct this problem.\n");
#else
		(void) printf("Sorry, the \"ncftpbatch\" program could not be found.\nThis program must be installed and in your PATH in order to use this feature.\n");
#endif
		return (-1);
	}
	return (0);
}	/* SpoolCheck */



/* Show and/or change customizable program settings. These changes are saved
 * at the end of the program's run.
 */
void
BGStartCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int i, n;

	ARGSUSED(gUnusedArg);
	if (SpoolCheck() < 0)
		return;

	if ((argc < 2) || ((n = atoi(argv[1])) < 2)) {
		RunBatch(0, &gConn);
		(void) printf("Background process started.\n");
#if defined(WIN32) || defined(_WINDOWS)
#else
		(void) printf("Watch the \"%s/batchlog\" file to see how it is progressing.\n", gOurDirectoryPath);
#endif
	} else {
		for (i=0; i<n; i++)
			RunBatch(0, &gConn);
		(void) printf("Background processes started.\n");
#if defined(WIN32) || defined(_WINDOWS)
#else
		(void) printf("Watch the \"%s/batchlog\" file to see how it is progressing.\n", gOurDirectoryPath);
#endif
	}
}	/* BGStart */




/* This commands lets the user change the umask, if the server supports it. */
/* (bgget) */
void
SpoolGetCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int opt;
	int renameMode = 0;
	int recurseFlag = kRecursiveNo;
	int rc;
	int i;
	int xtype = gBm.xferType;
	int nD = 0;
	int deleteFlag = kDeleteNo;
	time_t when = 0;
	char ldir[256];
	char pattern[256];
	char *lname;
	LineList ll;
	LinePtr lp;

	ARGSUSED(gUnusedArg);

	if ((gSavePasswords <= 0) && ((strcmp(gConn.user, "anonymous") != 0) && (strcmp(gConn.user, "ftp") != 0))) {
		(void) printf("Sorry, spooling isn't allowed when you're not logged in anonymously, because\nthe spool files would need to save your password.\n\nYou can override this by doing a \"set save-passwords yes\" if you're willing\nto live with the consequences.\n");
		return;
	} else if (SpoolCheck() < 0) {
		return;
	}

	GetoptReset();
	while ((opt = Getopt(argc, argv, "@:azfrRD")) >= 0) switch (opt) {
		case '@':
			when = GetStartSpoolDate(gOptArg);
			if ((when == (time_t) -1) || (when == (time_t) 0)) {
				(void) fprintf(stderr, "Bad date.  It must be expressed as one of the following:\n\tYYYYMMDDHHMMSS\t\n\t\"now + N hours|min|sec|days\"\n\tHH:MM\n\nNote:  Do not forget to quote the entire argument for the offset option.\nExample:  bgget -@ \"now + 15 min\" ...\n");
				return;
			}
			break;
		case 'a':
			xtype = kTypeAscii;
			break;
		case 'z':
			/* Special flag that lets you specify the
			 * destination file.  Normally a "get" will
			 * write the file by the same name as the
			 * remote file's basename.
			 */
			renameMode = 1;
			break;
		case 'r':
		case 'R':
			/* If the item is a directory, get the
			 * directory and all its contents.
			 */
			recurseFlag = kRecursiveYes;
			break;
		case 'D':
			/* You can delete the remote file after
			 * you downloaded it successfully by using
			 * the -DD option.  It requires two -D's
			 * to minimize the odds of accidentally
			 * using a single -D.
			 */
			nD++;
			break;
		default:
			PrintCmdUsage(cmdp);
			return;
	}

	if (nD >= 2)
		deleteFlag = kDeleteYes;

	if (FTPGetLocalCWD(ldir, sizeof(ldir)) == NULL) {
		perror("could not get current local directory");
		return;
	}

	if (renameMode != 0) {
		if (gOptInd > argc - 2) {
			PrintCmdUsage(cmdp);
			return;
		}
		rc = SpoolX(
			"get",
			argv[gOptInd],
			gRemoteCWD,
			argv[gOptInd + 1],
			ldir,
			gConn.host,
			gConn.ip,
			gConn.port,
			gConn.user,
			gConn.pass,
			xtype,
			recurseFlag,
			deleteFlag,
			gConn.dataPortMode,
			NULL,
			NULL,
			NULL,
			when
		);
		if (rc == 0) {
			Trace(-1, "  + Spooled: get %s as %s\n", argv[gOptInd], argv[gOptInd]);
		}
	} else {
		for (i=gOptInd; i<argc; i++) {
			STRNCPY(pattern, argv[i]);
			StrRemoveTrailingSlashes(pattern);
			InitLineList(&ll);
			rc = FTPRemoteGlob(&gConn, &ll, pattern, (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
			if (rc < 0) {
				FTPPerror(&gConn, rc, kErrGlobFailed, argv[0], pattern);
			} else {
				for (lp = ll.first; lp != NULL; lp = lp->next) {
					if (lp->line != NULL) {
						lname = strrchr(lp->line, '/');
						if (lname == NULL)
							lname = lp->line;
						else
							lname++;
						rc = SpoolX(
							"get",
							lp->line,
							gRemoteCWD,
							lname,
							ldir,
							gConn.host,
							gConn.ip,
							gConn.port,
							gConn.user,
							gConn.pass,
							xtype,
							recurseFlag,
							deleteFlag,
							gConn.dataPortMode,
							NULL,
							NULL,
							NULL,
							when
						);
						if (rc == 0) {
							Trace(-1, "  + Spooled: get %s\n", lp->line);
						}
					}
				}
			}
			DisposeLineListContents(&ll);
		}
	}
}	/* SpoolGetCmd */




/* This commands lets the user change the umask, if the server supports it. */
/* (bgput) */
void
SpoolPutCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int opt;
	int renameMode = 0;
	int recurseFlag = kRecursiveNo;
	int rc;
	int i;
	int xtype = gBm.xferType;
	int nD = 0;
	int deleteFlag = kDeleteNo;
	time_t when = 0;
	char ldir[256];
	char pattern[256];
	LineList ll;
	LinePtr lp;
	char *rname;

	ARGSUSED(gUnusedArg);

	if ((gSavePasswords <= 0) && ((strcmp(gConn.user, "anonymous") != 0) && (strcmp(gConn.user, "ftp") != 0))) {
		(void) printf("Sorry, spooling isn't allowed when you're not logged in anonymously, because\nthe spool files would need to save your password.\n\nYou can override this by doing a \"set save-passwords yes\" if you're willing\nto live with the consequences.\n");
		return;
	} else if (SpoolCheck() < 0) {
		return;
	}

	GetoptReset();
	while ((opt = Getopt(argc, argv, "@:azrRD")) >= 0) switch (opt) {
		case '@':
			when = GetStartSpoolDate(gOptArg);
			if ((when == (time_t) -1) || (when == (time_t) 0)) {
				(void) fprintf(stderr, "Bad date.  It must be expressed as one of the following:\n\tYYYYMMDDHHMMSS\t\n\t\"now + N hours|min|sec|days\"\n\tHH:MM\n\nNote:  Do not forget to quote the entire argument for the offset option.\nExample:  bgget -@ \"now + 15 min\" ...\n");
				return;
			}
			break;
		case 'a':
			xtype = kTypeAscii;
			break;
		case 'z':
			/* Special flag that lets you specify the
			 * destination file.  Normally a "get" will
			 * write the file by the same name as the
			 * remote file's basename.
			 */
			renameMode = 1;
			break;
		case 'r':
		case 'R':
			/* If the item is a directory, get the
			 * directory and all its contents.
			 */
			recurseFlag = kRecursiveYes;
			break;
		case 'D':
			/* You can delete the remote file after
			 * you downloaded it successfully by using
			 * the -DD option.  It requires two -D's
			 * to minimize the odds of accidentally
			 * using a single -D.
			 */
			nD++;
			break;
		default:
			PrintCmdUsage(cmdp);
			return;
	}

	if (nD >= 2)
		deleteFlag = kDeleteYes;

	if (FTPGetLocalCWD(ldir, sizeof(ldir)) == NULL) {
		perror("could not get current local directory");
		return;
	}

	if (renameMode != 0) {
		if (gOptInd > argc - 2) {
			PrintCmdUsage(cmdp);
			return;
		}
		rc = SpoolX(
			"put",
			argv[gOptInd + 1],
			gRemoteCWD,
			argv[gOptInd + 0],
			ldir,
			gConn.host,
			gConn.ip,
			gConn.port,
			gConn.user,
			gConn.pass,
			xtype,
			recurseFlag,
			deleteFlag,
			gConn.dataPortMode,
			NULL,
			NULL,
			NULL,
			when
		);
		if (rc == 0) {
			Trace(-1, "  + Spooled: put %s as %s\n", argv[gOptInd], argv[gOptInd]);
		}
	} else {
		for (i=gOptInd; i<argc; i++) {
			STRNCPY(pattern, argv[i]);
			StrRemoveTrailingSlashes(pattern);
			InitLineList(&ll);
			rc = FTPLocalGlob(&gConn, &ll, pattern, (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
			if (rc < 0) {
				FTPPerror(&gConn, rc, kErrGlobFailed, "local glob", pattern);
			} else {
				for (lp = ll.first; lp != NULL; lp = lp->next) {
					if (lp->line != NULL) {
						rname = strrchr(lp->line, '/');
						if (rname == NULL)
							rname = lp->line;
						else
							rname++;
						rc = SpoolX(
							"put",
							rname,
							gRemoteCWD,
							lp->line,
							ldir,
							gConn.host,
							gConn.ip,
							gConn.port,
							gConn.user,
							gConn.pass,
							xtype,
							recurseFlag,
							deleteFlag,
							gConn.dataPortMode,
							NULL,
							NULL,
							NULL,
							when
						);
						if (rc == 0) {
							Trace(-1, "  + Spooled: put %s\n", lp->line);
						}
					}
				}
			}
			DisposeLineListContents(&ll);
		}
	}
}	/* SpoolGetCmd */




/* This commands lets the user change the umask, if the server supports it. */
void
SymlinkCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int result;

	ARGSUSED(gUnusedArg);
	result = FTPSymlink(&gConn, argv[1], argv[2]);
	if (result < 0)
		FTPPerror(&gConn, result, kErrSYMLINKFailed, "symlink", argv[1]);

	/* Really should just flush only the modified directories... */
	FlushLsCache();
}	/* SymlinkCmd */




/* This commands lets the user change the transfer type to use. */
void
TypeCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int c;
	int result;
	const char *cs;

	ARGSUSED(gUnusedArg);
	if (argc < 2) {
		c = argv[0][0];
		if (c == 't') {
			if (gBm.xferType == kTypeAscii) {
				c = kTypeAscii;
				cs = "ASCII";
			} else if (gBm.xferType == kTypeEbcdic) {
				c = kTypeEbcdic;
				cs = "EBCDIC";
			} else {
				c = kTypeBinary;
				cs = "binary/image";
			}
			Trace(-1, "Type is %c (%s).\n", c, cs);
		} else {
			result = FTPSetTransferType(&gConn, c);
			if (result < 0) {
				FTPPerror(&gConn, result, kErrTYPEFailed, "Type", argv[1]);
			} else {
				gBm.xferType = gConn.curTransferType;
			}
		}
	} else {
		c = argv[1][0];
		result = FTPSetTransferType(&gConn, c);
		if (result < 0) {
			FTPPerror(&gConn, result, kErrTYPEFailed, "Type", argv[1]);
		} else {
			gBm.xferType = gConn.curTransferType;
		}
	}
}	/* TypeCmd */




/* This commands lets the user change the umask, if the server supports it. */
void
UmaskCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	int result;

	ARGSUSED(gUnusedArg);
	result = FTPUmask(&gConn, argv[1]);
	if (result < 0)
		FTPPerror(&gConn, result, kErrUmaskFailed, "umask", argv[1]);
}	/* UmaskCmd */




/* Show the version information. */
void
VersionCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
{
	ARGSUSED(gUnusedArg);
	(void) printf("Version:          %s\n", gVersion + 5);
	(void) printf("Author:           Mike Gleason (ncftp@ncftp.com)\n");
#ifndef BETA
	(void) printf("Archived at:      ftp://ftp.ncftp.com/ncftp/\n");
#endif
	(void) printf("Library Version:  %s\n", gLibNcFTPVersion + 5);
#ifdef __DATE__
	(void) printf("Compile Date:     %s\n", __DATE__);
#endif
	if (gOS[0] != '\0')
		(void) printf("Platform:         %s\n", gOS);
}	/* VersionCmd */
