1 /* cmds.c
2  *
3  * Copyright (c) 1992-2001 by Mike Gleason.
4  * All rights reserved.
5  *
6  */
7 
8 #include "syshdrs.h"
9 #include "shell.h"
10 #include "util.h"
11 #include "ls.h"
12 #include "bookmark.h"
13 #include "cmds.h"
14 #include "main.h"
15 #include "trace.h"
16 #include "log.h"
17 #include "pref.h"
18 #include "spool.h"
19 #include "getline.h"
20 #include "readln.h"
21 #include "getopt.h"
22 
23 /* This was the directory path when the user logged in.  For anonymous users,
24  * this should be "/", but for real users, it should be their home directory.
25  * This variable is used later when we want to calculate a relative path
26  * from the starting directory.
27  */
28 char gStartDir[512];
29 
30 /* The pathname to the current working directory.  This always needs to be
31  * current, so whenever a directory change is done this should be changed.
32  */
33 char gRemoteCWD[512];
34 
35 /* Same, but the previous directory the user was in, or empty string if
36  * there is none.
37  */
38 char gPrevRemoteCWD[512];
39 
40 /* Another buffer we use just temporarily when switching directories. */
41 char gScratchCWD[512];
42 
43 /* The only reason we do this is to get gcc/lint to shut up
44  * about unused parameters.
45  */
46 int gUnusedArg;
47 #define ARGSUSED(x) x = (argc != 0) || (argv != 0) || (cmdp != 0) || (aip != 0)
48 
49 /* Used temporarily, but put it here because it's big. */
50 FTPConnectionInfo gTmpURLConn;
51 
52 /* If the user doesn't want to be prompted for a batch of files,
53  * they can tell us to answer this answer for each item in the batch.
54  */
55 int gResumeAnswerAll;
56 
57 extern FTPLibraryInfo gLib;
58 extern FTPConnectionInfo gConn;
59 extern char gOurDirectoryPath[];
60 extern Command gCommands[];
61 extern char gVersion[];
62 extern size_t gNumCommands;
63 extern int gDebug, gDoneApplication;
64 extern char *gOptArg;
65 extern int gOptInd, gGotSig;
66 extern int gFirstTimeUser;
67 extern unsigned int gFirewallPort;
68 extern int gFirewallType;
69 extern char gFirewallHost[64];
70 extern char gFirewallUser[32];
71 extern char gFirewallPass[32];
72 extern char gFirewallExceptionList[];
73 extern char gPager[], gHome[], gShell[];
74 extern char gOS[];
75 extern int gAutoResume, gRedialDelay;
76 extern int gAutoSaveChangesToExistingBookmarks;
77 extern Bookmark gBm;
78 extern int gLoadedBm, gConfirmClose, gSavePasswords, gScreenColumns;
79 extern char gLocalCWD[512], gPrevLocalCWD[512], gOurInstallationPath[];
80 extern int gMayCancelJmp;
81 #if defined(WIN32) || defined(_WINDOWS)
82 #elif defined(HAVE_SIGSETJMP)
83 extern sigjmp_buf gCancelJmp;
84 #else	/* HAVE_SIGSETJMP */
85 extern jmp_buf gCancelJmp;
86 #endif	/* HAVE_SIGSETJMP */
87 
88 
89 
90 
91 /* Open the users $PAGER, or just return stdout.  Make sure to use
92  * ClosePager(), and not fclose/pclose directly.
93  */
94 static FILE *
OpenPager(void)95 OpenPager(void)
96 {
97 	FILE *fp;
98 	char *pprog;
99 
100 	(void) fflush(stdout);
101 	pprog = gPager;
102 	fp = popen((pprog[0] == '\0') ? "more" : pprog, "w");
103 	if (fp == NULL)
104 		return (stdout);
105 	return (fp);
106 }	/* OpenPager */
107 
108 
109 
110 
111 /* Close (maybe) a file previously created by OpenPager. */
112 static void
ClosePager(FILE * pagerfp)113 ClosePager(FILE *pagerfp)
114 {
115 #ifdef SIGPIPE
116 	sigproc_t osigpipe;
117 #endif
118 
119 	if ((pagerfp != NULL) && (pagerfp != stdout)) {
120 #ifdef SIGPIPE
121 		osigpipe = (sigproc_t) NcSignal(SIGPIPE, SIG_IGN);
122 #endif
123 		(void) pclose(pagerfp);
124 #ifdef SIGPIPE
125 		(void) NcSignal(SIGPIPE, osigpipe);
126 #endif
127 	}
128 }	/* ClosePager */
129 
130 
131 
132 
133 /* Fills in the bookmarkName field of the Bookmark. */
134 int
PromptForBookmarkName(BookmarkPtr bmp)135 PromptForBookmarkName(BookmarkPtr bmp)
136 {
137 	char dfltname[64];
138 	char bmname[64];
139 
140 	DefaultBookmarkName(dfltname, sizeof(dfltname), gConn.host);
141 	if (dfltname[0] == '\0') {
142 		(void) printf("Enter a name for this bookmark: ");
143 	} else {
144 		(void) printf("Enter a name for this bookmark, or hit enter for \"%s\": ", dfltname);
145 	}
146 	fflush(stdin);
147 	(void) FGets(bmname, sizeof(bmname), stdin);
148 	if (bmname[0] != '\0') {
149 		(void) STRNCPY(bmp->bookmarkName, bmname);
150 		return (0);
151 	} else if (dfltname[0] != '\0') {
152 		(void) STRNCPY(bmp->bookmarkName, dfltname);
153 		return (0);
154 	}
155 	return (-1);
156 }	/* PromptForBookmarkName */
157 
158 
159 
160 void
CurrentURL(char * dst,size_t dsize,int showpass)161 CurrentURL(char *dst, size_t dsize, int showpass)
162 {
163 	Bookmark bm;
164 	char dir[160];
165 
166 	memset(&bm, 0, sizeof(bm));
167 	(void) STRNCPY(bm.name, gConn.host);
168 	if ((gConn.user[0] != '\0') && (! STREQ(gConn.user, "anonymous")) && (! STREQ(gConn.user, "ftp"))) {
169 		(void) STRNCPY(bm.user, gConn.user);
170 		(void) STRNCPY(bm.pass, (showpass == 0) ? "PASSWORD" : gConn.pass);
171 		(void) STRNCPY(bm.acct, gConn.acct);
172 	}
173 
174 	bm.port = gConn.port;
175 
176 	/* We now save relative paths, because the pathname in URLs are
177 	 * relative by nature.  This makes non-anonymous FTP URLs shorter
178 	 * because it doesn't have to include the pathname of their
179 	 * home directory.
180 	 */
181 	(void) STRNCPY(dir, gRemoteCWD);
182 	AbsoluteToRelative(bm.dir, sizeof(bm.dir), dir, gStartDir, strlen(gStartDir));
183 
184 	BookmarkToURL(&bm, dst, dsize);
185 }	/* CurrentURL */
186 
187 
188 
189 
190 /* Fills in the fields of the Bookmark structure, based on the FTP current
191  * session.
192  */
193 void
FillBookmarkInfo(BookmarkPtr bmp)194 FillBookmarkInfo(BookmarkPtr bmp)
195 {
196 	char dir[160];
197 
198 	(void) STRNCPY(bmp->name, gConn.host);
199 	if ((STREQ(gConn.user, "anonymous")) || (STREQ(gConn.user, "ftp"))) {
200 		bmp->user[0] = '\0';
201 		bmp->pass[0] = '\0';
202 		bmp->acct[0] = '\0';
203 	} else {
204 		(void) STRNCPY(bmp->user, gConn.user);
205 		(void) STRNCPY(bmp->pass, gConn.pass);
206 		(void) STRNCPY(bmp->acct, gConn.acct);
207 	}
208 
209 	/* We now save relative paths, because the pathname in URLs are
210 	 * relative by nature.  This makes non-anonymous FTP URLs shorter
211 	 * because it doesn't have to include the pathname of their
212 	 * home directory.
213 	 */
214 	(void) STRNCPY(dir, gRemoteCWD);
215 	AbsoluteToRelative(bmp->dir, sizeof(bmp->dir), dir, gStartDir, strlen(gStartDir));
216 	bmp->port = gConn.port;
217 	(void) time(&bmp->lastCall);
218 	bmp->hasSIZE = gConn.hasSIZE;
219 	bmp->hasMDTM = gConn.hasMDTM;
220 	bmp->hasPASV = gConn.hasPASV;
221 	bmp->hasUTIME = gConn.hasUTIME;
222 	if (gFirewallType == kFirewallNotInUse)
223 		(void) STRNCPY(bmp->lastIP, gConn.ip);
224 }	/* FillBookmarkInfo */
225 
226 
227 
228 
229 /* Saves the current FTP session settings as a bookmark. */
230 void
SaveCurrentAsBookmark(void)231 SaveCurrentAsBookmark(void)
232 {
233 	int saveBm;
234 	char ans[64];
235 
236 	/* gBm.bookmarkName must already be set. */
237 	FillBookmarkInfo(&gBm);
238 
239 	saveBm = gSavePasswords;
240 	if (gLoadedBm != 0)
241 		saveBm = 1;
242 	if ((saveBm < 0) && (gBm.pass[0] != '\0')) {
243 		(void) printf("\n\nYou logged into this site using a password.\nWould you like to save the password with this bookmark?\n\n");
244 		(void) printf("Save? [no] ");
245 		(void) memset(ans, 0, sizeof(ans));
246 		fflush(stdin);
247 		(void) fgets(ans, sizeof(ans) - 1, stdin);
248 		if ((saveBm = StrToBool(ans)) == 0) {
249 			(void) printf("\nNot saving the password.\n");
250 		}
251 	}
252 	if (PutBookmark(&gBm, saveBm) < 0) {
253 		(void) fprintf(stderr, "Could not save bookmark.\n");
254 	} else {
255 		/* Also marks whether we saved it. */
256 		gLoadedBm = 1;
257 		(void) printf("Bookmark \"%s\" saved.\n", gBm.bookmarkName);
258 
259 		ReCacheBookmarks();
260 	}
261 }	/* SaveCurrentAsBookmark */
262 
263 
264 
265 
266 /* If the user did not explicitly bookmark this site already, ask
267  * the user if they want to save one.
268  */
269 void
SaveUnsavedBookmark(void)270 SaveUnsavedBookmark(void)
271 {
272 	char url[256];
273 	char ans[64];
274 	int c;
275 
276 	if ((gConfirmClose != 0) && (gLoadedBm == 0) && (gOurDirectoryPath[0] != '\0')) {
277 		FillBookmarkInfo(&gBm);
278 		BookmarkToURL(&gBm, url, sizeof(url));
279 		(void) printf("\n\nYou have not saved a bookmark for this site.\n");
280 		(void) sleep(1);
281 		(void) printf("\nWould you like to save a bookmark to:\n\t%s\n\n", url);
282 		for (;;) {
283 			(void) printf("Save? (yes/no) ");
284 			(void) memset(ans, 0, sizeof(ans));
285 			fflush(stdin);
286 			if (fgets(ans, sizeof(ans) - 1, stdin) == NULL) {
287 				c = 'n';
288 				break;
289 			}
290 			c = ans[0];
291 			if ((c == 'n') || (c == 'y'))
292 				break;
293 			if (c == 'N') {
294 				c = 'n';
295 				break;
296 			} else if (c == 'Y') {
297 				c = 'y';
298 				break;
299 			}
300 		}
301 		if (c == 'n') {
302 			(void) printf("Not saved.  (If you don't want to be asked this, \"set confirm-close no\")\n\n\n");
303 
304 		} else if (PromptForBookmarkName(&gBm) < 0) {
305 			(void) printf("Nevermind.\n");
306 		} else {
307 			SaveCurrentAsBookmark();
308 		}
309 	} else if ((gLoadedBm == 1) && (gOurDirectoryPath[0] != '\0') && (strcmp(gOurDirectoryPath, gBm.dir) != 0)) {
310 		/* Bookmark has changed. */
311 		if (gAutoSaveChangesToExistingBookmarks != 0) {
312 			SaveCurrentAsBookmark();
313 		}
314 	}
315 }	/* SaveUnsavedBookmark */
316 
317 
318 
319 /* Save the current host session settings for later as a "bookmark", which
320  * will be referred to by a bookmark abbreviation name.
321  */
322 void
BookmarkCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)323 BookmarkCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
324 {
325 	/* The only reason we do this is to get gcc/lint to shut up
326 	 * about unused parameters.
327 	 */
328 	ARGSUSED(gUnusedArg);
329 
330 	if (gOurDirectoryPath[0] == '\0') {
331 		(void) printf("Sorry, configuration information is not saved for this user.\n");
332 	} else if ((argc <= 1) || (argv[1][0] == '\0')) {
333 		/* No name specified on the command line. */
334 		if (gBm.bookmarkName[0] == '\0') {
335 			/* Not previously bookmarked. */
336 			if (PromptForBookmarkName(&gBm) < 0) {
337 				(void) printf("Nevermind.\n");
338 			} else {
339 				SaveCurrentAsBookmark();
340 			}
341 		} else {
342 			/* User wants to update an existing bookmark. */
343 			SaveCurrentAsBookmark();
344 		}
345 	} else {
346 		(void) STRNCPY(gBm.bookmarkName, argv[1]);
347 		SaveCurrentAsBookmark();
348 	}
349 }	/* BookmarkCmd */
350 
351 
352 
353 
354 /* Dump a remote file to the screen. */
355 void
CatCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)356 CatCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
357 {
358 	int result;
359 	int i;
360 
361 	ARGSUSED(gUnusedArg);
362 	for (i=1; i<argc; i++) {
363 		result = FTPGetOneFile2(&gConn, argv[i], NULL, kTypeAscii, STDOUT_FILENO, kResumeNo, kAppendNo);
364 		FTPPerror(&gConn, result, kErrCouldNotStartDataTransfer, "cat", argv[i]);
365 	}
366 }	/* CatCmd */
367 
368 
369 
370 static void
NcFTPCdResponseProc(const FTPCIPtr cipUNUSED,ResponsePtr rp)371 NcFTPCdResponseProc(const FTPCIPtr cipUNUSED, ResponsePtr rp)
372 {
373 	LinePtr lp;
374 	LineListPtr llp;
375 
376 	gUnusedArg = (cipUNUSED != NULL);
377 	if ((rp->printMode & kResponseNoPrint) != 0)
378 		return;
379 	llp = &rp->msg;
380 	for (lp = llp->first; lp != NULL; lp = lp->next) {
381 		if ((lp == llp->first) && (rp->codeType == 2)) {
382 			if (ISTRNCMP(lp->line, "CWD command", 11) == 0)
383 				continue;
384 			if (lp->line[0] == '"')
385 				continue;	/* "/pub/foo" is... */
386 		}
387 		(void) printf("%s\n", lp->line);
388 	}
389 }	/* NcFTPCdResponseProc */
390 
391 
392 
393 
394 /* Manually print a response obtained from the remote FTP user. */
395 void
PrintResp(LineListPtr llp)396 PrintResp(LineListPtr llp)
397 {
398 	LinePtr lp;
399 
400 	if (llp != NULL) {
401 		for (lp = llp->first; lp != NULL; lp = lp->next) {
402 			if ((lp == llp->first) && (ISTRNCMP(lp->line, "CWD command", 11) == 0))
403 				continue;
404 			(void) printf("%s\n", lp->line);
405 		}
406 	}
407 }	/* PrintResp */
408 
409 
410 
411 
412 /* Do a chdir, and update our notion of the current directory.
413  * Some servers return it back as part of the CWD response,
414  * otherwise do a CWD command followed by a PWD.
415  */
416 int
nFTPChdirAndGetCWD(const FTPCIPtr cip,const char * cdCwd,const int quietMode)417 nFTPChdirAndGetCWD(const FTPCIPtr cip, const char *cdCwd, const int quietMode)
418 {
419 	ResponsePtr rp;
420 	size_t cdCwdLen;
421 	int result;
422 #ifdef USE_WHAT_SERVER_SAYS_IS_CWD
423 	int foundcwd;
424 	char *l, *r;
425 #endif
426 
427 	if (cip == NULL)
428 		return (kErrBadParameter);
429 	if (strcmp(cip->magic, kLibraryMagic))
430 		return (kErrBadMagic);
431 
432 	if ((cdCwd == NULL) || (cdCwd[0] == '\0')) {
433 		result = kErrInvalidDirParam;
434 		cip->errNo = kErrInvalidDirParam;
435 	} else {
436 		rp = InitResponse();
437 		if (rp == NULL) {
438 			result = kErrMallocFailed;
439 			cip->errNo = kErrMallocFailed;
440 			/* Error(cip, kDontPerror, "Malloc failed.\n"); */
441 		} else {
442 			cdCwdLen = strlen(cdCwd);
443 			if (strcmp(cdCwd, "..") == 0) {
444 				result = RCmd(cip, rp, "CDUP");
445 			} else {
446 				result = RCmd(cip, rp, "CWD %s", cdCwd);
447 			}
448 			if (result == 2) {
449 #ifdef USE_WHAT_SERVER_SAYS_IS_CWD
450 				(void) STRNCPY(gScratchCWD, gRemoteCWD);
451 				foundcwd = 0;
452 				if ((r = strrchr(rp->msg.first->line, '"')) != NULL) {
453 					/* "xxxx" is current directory.
454 					 * Strip out just the xxxx to copy into the remote cwd.
455 					 *
456 					 * This is nice because we didn't have to do a PWD.
457 					 */
458 					l = strchr(rp->msg.first->line, '"');
459 					if ((l != NULL) && (l != r) && (l == rp->msg.first->line)) {
460 						*r = '\0';
461 						++l;
462 						(void) Strncpy(gRemoteCWD, l, sizeof(gRemoteCWD));
463 						*r = '"';	/* Restore, so response prints correctly. */
464 						foundcwd = 1;
465 						result = kNoErr;
466 					}
467 				}
468 				if (quietMode)
469 					rp->printMode |= kResponseNoPrint;
470 				NcFTPCdResponseProc(cip, rp);
471 				DoneWithResponse(cip, rp);
472 				if (foundcwd == 0) {
473 					result = FTPGetCWD(cip, gRemoteCWD, sizeof(gRemoteCWD));
474 					if (result != kNoErr) {
475 						PathCat(gRemoteCWD, sizeof(gRemoteCWD), gScratchCWD, cdCwd);
476 						result = kNoErr;
477 					}
478 				}
479 #else /* USE_CLIENT_SIDE_CALCULATED_CWD */
480 				if (quietMode)
481 					rp->printMode |= kResponseNoPrint;
482 				NcFTPCdResponseProc(cip, rp);
483 				DoneWithResponse(cip, rp);
484 				(void) STRNCPY(gScratchCWD, gRemoteCWD);
485 				PathCat(gRemoteCWD, sizeof(gRemoteCWD), gScratchCWD, cdCwd);
486 				result = kNoErr;
487 #endif
488 			} else if (result > 0) {
489 				result = kErrCWDFailed;
490 				cip->errNo = kErrCWDFailed;
491 				DoneWithResponse(cip, rp);
492 			} else {
493 				DoneWithResponse(cip, rp);
494 			}
495 		}
496 	}
497 	return (result);
498 }	/* nFTPChdirAndGetCWD */
499 
500 
501 
502 
503 int
Chdirs(FTPCIPtr cip,const char * const cdCwd)504 Chdirs(FTPCIPtr cip, const char *const cdCwd)
505 {
506 	char *cp, *startcp;
507 	int result;
508 	int lastSubDir;
509 
510 	if (cip == NULL)
511 		return (kErrBadParameter);
512 	if (strcmp(cip->magic, kLibraryMagic))
513 		return (kErrBadMagic);
514 
515 	if (cdCwd == NULL) {
516 		result = kErrInvalidDirParam;
517 		cip->errNo = kErrInvalidDirParam;
518 		return result;
519 	}
520 
521 	if ((cdCwd[0] == '\0') || (strcmp(cdCwd, ".") == 0)) {
522 		result = 0;
523 		return (result);
524 	}
525 
526 	cp = cip->buf;
527 	cp[cip->bufSize - 2] = '\0';
528 	if ((cdCwd[0] == '.') && (cdCwd[1] == '.') && ((cdCwd[2] == '\0') || IsLocalPathDelim(cdCwd[2]))) {
529 		PathCat(cip->buf, cip->bufSize, gRemoteCWD, cdCwd);
530 	} else {
531 		(void) Strncpy(cip->buf, cdCwd, cip->bufSize);
532 	}
533 	if (cp[cip->bufSize - 2] != '\0')
534 		return (kErrBadParameter);
535 
536 	StrRemoveTrailingLocalPathDelim(cp);
537 	do {
538 		startcp = cp;
539 		cp = StrFindLocalPathDelim(cp + 0);
540 		if (cp != NULL) {
541 			*cp++ = '\0';
542 		}
543 		lastSubDir = (cp == NULL);
544 		result = nFTPChdirAndGetCWD(cip, (*startcp != '\0') ? startcp : "/", lastSubDir ? 0 : 1);
545 		if (result < 0) {
546 			cip->errNo = result;
547 		}
548 	} while ((!lastSubDir) && (result == 0));
549 
550 	return (result);
551 }	/* Chdirs */
552 
553 
554 
555 
556 /* Remote change of working directory command. */
557 void
ChdirCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)558 ChdirCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
559 {
560 	int result;
561 	LineList ll;
562 	LinePtr lp;
563 
564 	ARGSUSED(gUnusedArg);
565 
566 	if (argc <= 1) {
567 		if (gStartDir[0] != '\0') {
568 			(void) STRNCPY(gPrevRemoteCWD, gRemoteCWD);
569 			result = Chdirs(&gConn, gStartDir);
570 			if (result != kNoErr) {
571 				/* State is incoherent if this happens! */
572 				FTPPerror(&gConn, result, kErrCWDFailed, "Could not chdir to", gStartDir);
573 			}
574 		} else {
575 			PrintCmdUsage(cmdp);
576 		}
577 	} else {
578 		InitLineList(&ll);
579 		result = FTPRemoteGlob(&gConn, &ll, argv[1], (aip->noglobargv[1] != 0) ? kGlobNo: kGlobYes);
580 		if (result < 0) {
581 			FTPPerror(&gConn, result, kErrGlobFailed, argv[0], argv[1]);
582 		} else {
583 			lp = ll.first;
584 			if ((lp != NULL) && (lp->line != NULL)) {
585 				if ((strcmp(lp->line, "-") == 0) && (gPrevRemoteCWD[0] != '\0')) {
586 					free(lp->line);
587 					lp->line = StrDup(gPrevRemoteCWD);
588 					if (lp->line == NULL) {
589 						result = kErrMallocFailed;
590 						gConn.errNo = kErrMallocFailed;
591 					} else {
592 						(void) STRNCPY(gPrevRemoteCWD, gRemoteCWD);
593 						result = Chdirs(&gConn, lp->line);
594 					}
595 				} else {
596 					(void) STRNCPY(gPrevRemoteCWD, gRemoteCWD);
597 					result = Chdirs(&gConn, lp->line);
598 				}
599 				if (result != kNoErr)
600 					FTPPerror(&gConn, result, kErrCWDFailed, "Could not chdir to", lp->line);
601 			}
602 		}
603 		DisposeLineListContents(&ll);
604 	}
605 }	/* ChdirCmd */
606 
607 
608 
609 
610 /* Chmod files on the remote host, if it supports it. */
611 void
ChmodCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)612 ChmodCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
613 {
614 	int i, result;
615 
616 	ARGSUSED(gUnusedArg);
617 	for (i=2; i<argc; i++) {
618 		result = FTPChmod(
619 				&gConn, argv[i], argv[1],
620 				(aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes
621 			);
622 		if (result < 0) {
623 			FTPPerror(&gConn, result, kErrChmodFailed, "chmod", argv[i]);
624 			/* but continue */
625 		}
626 	}
627 
628 	/* Really should just flush only the modified directories... */
629 	FlushLsCache();
630 }	/* ChmodCmd */
631 
632 
633 
634 
635 /* Close the current session to a remote FTP server. */
636 void
CloseCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)637 CloseCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
638 {
639 	ARGSUSED(gUnusedArg);
640 	if (gConn.connected == 0)
641 		(void) printf("Already closed.\n");
642 	else
643 		CloseHost();
644 }	/* CloseCmd */
645 
646 
647 
648 /* User interface to the program's debug-mode setting. */
649 void
DebugCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)650 DebugCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
651 {
652 	ARGSUSED(gUnusedArg);
653 	if (argc > 1)
654 		SetDebug(atoi(argv[1]));
655 	else
656 		SetDebug(!gDebug);
657 }	/* DebugCmd */
658 
659 
660 
661 
662 /* Delete files on the remote host. */
663 void
DeleteCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)664 DeleteCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
665 {
666 	int result;
667 	int i, c;
668 	int recursive = kRecursiveNo;
669 
670 	ARGSUSED(gUnusedArg);
671 	GetoptReset();
672 	while ((c = Getopt(argc, argv, "rf")) > 0) switch(c) {
673 		case 'r':
674 			recursive = kRecursiveYes;
675 			break;
676 		case 'f':
677 			/* ignore */
678 			break;
679 		default:
680 			PrintCmdUsage(cmdp);
681 			return;
682 	}
683 
684 	for (i=gOptInd; i<argc; i++) {
685 		result = FTPDelete(
686 				&gConn, argv[i], recursive,
687 				(aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes
688 			);
689 		if (result < 0) {
690 			FTPPerror(&gConn, result, kErrDELEFailed, "delete", argv[i]);
691 			/* but continue */
692 		}
693 	}
694 
695 	/* Really should just flush only the modified directories... */
696 	FlushLsCache();
697 }	/* DeleteCmd */
698 
699 
700 
701 
702 /* Command shell echo command.  This is mostly useful for testing the command
703  * shell, as a sample command which prints some output.
704  */
705 void
EchoCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)706 EchoCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
707 {
708 	int i;
709 	int result;
710 	int np = 0;
711 	LineList ll;
712 	LinePtr lp;
713 
714 	ARGSUSED(gUnusedArg);
715 	for (i=1; i<argc; i++) {
716 		InitLineList(&ll);
717 		result = FTPLocalGlob(&gConn, &ll, argv[i], (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
718 		if (result < 0) {
719 			FTPPerror(&gConn, result, kErrGlobFailed, "local glob", argv[i]);
720 		} else {
721 			for (lp = ll.first; lp != NULL; lp = lp->next) {
722 				if (lp->line != NULL) {
723 					if (np > 0)
724 						(void) printf(" ");
725 					(void) printf("%s", lp->line);
726 					np++;
727 				}
728 			}
729 		}
730 		DisposeLineListContents(&ll);
731 	}
732 	(void) printf("\n");
733 }	/* EchoCmd */
734 
735 
736 
737 
738 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)739 NcFTPConfirmResumeDownloadProc(
740 	const char *volatile *localpath,
741 	volatile longest_int localsize,
742 	volatile time_t localmtime,
743 	const char *volatile remotepath,
744 	volatile longest_int remotesize,
745 	volatile time_t remotemtime,
746 	volatile longest_int *volatile startPoint)
747 {
748 	int zaction = kConfirmResumeProcSaidBestGuess;
749 	char tstr[80], ans[32];
750 	static char newname[128];	/* arrggh... static. */
751 
752 	if (gResumeAnswerAll != kConfirmResumeProcNotUsed)
753 		return (gResumeAnswerAll);
754 
755 	if (gAutoResume != 0)
756 		return (kConfirmResumeProcSaidBestGuess);
757 
758 	tstr[sizeof(tstr) - 1] = '\0';
759 	(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &localmtime));
760 	(void) printf(
761 #if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
762 		"\nThe local file \"%s\" already exists.\n\tLocal:  %12lld bytes, dated %s.\n",
763 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
764 		"\nThe local file \"%s\" already exists.\n\tLocal:  %12qd bytes, dated %s.\n",
765 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
766 		"\nThe local file \"%s\" already exists.\n\tLocal:  %12I64d bytes, dated %s.\n",
767 #else
768 		"\nThe local file \"%s\" already exists.\n\tLocal:  %12ld bytes, dated %s.\n",
769 #endif
770 		*localpath,
771 		localsize,
772 		tstr
773 	);
774 
775 	if ((remotemtime != kModTimeUnknown) && (remotesize != kSizeUnknown)) {
776 		(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &remotemtime));
777 		(void) printf(
778 #if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
779 			"\tRemote: %12lld bytes, dated %s.\n",
780 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
781 			"\tRemote: %12qd bytes, dated %s.\n",
782 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
783 			"\tRemote: %12I64d bytes, dated %s.\n",
784 #else
785 			"\tRemote: %12ld bytes, dated %s.\n",
786 #endif
787 			remotesize,
788 			tstr
789 		);
790 		if ((remotemtime == localmtime) && (remotesize == localsize)) {
791 			(void) printf("\t(Files are identical, skipped)\n\n");
792 			return (kConfirmResumeProcSaidSkip);
793 		}
794 	} else if (remotesize != kSizeUnknown) {
795 		(void) printf(
796 #if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
797 			"\tRemote: %12lld bytes, date unknown.\n",
798 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
799 			"\tRemote: %12qd bytes, date unknown.\n",
800 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
801 			"\tRemote: %12I64d bytes, date unknown.\n",
802 #else
803 			"\tRemote: %12ld bytes, date unknown.\n",
804 #endif
805 			remotesize
806 		);
807 	} else if (remotemtime != kModTimeUnknown) {
808 		(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &remotemtime));
809 		(void) printf(
810 			"\tRemote: size unknown, dated %s.\n",
811 			tstr
812 		);
813 	}
814 
815 	printf("\n");
816 	(void) memset(ans, 0, sizeof(ans));
817 	for (;;) {
818 		(void) printf("\t[O]verwrite?");
819 		if ((gConn.hasREST == kCommandAvailable) && (remotesize != kSizeUnknown) && (remotesize > localsize))
820 			printf("  [R]esume?");
821 		printf("  [A]ppend to?  [S]kip?  [N]ew Name?\n");
822 		(void) printf("\t[O!]verwrite all?");
823 		if ((gConn.hasREST == kCommandAvailable) && (remotesize != kSizeUnknown) && (remotesize > localsize))
824 			printf("  [R!]esume all?");
825 		printf("  [S!]kip all?  [C]ancel  > ");
826 		fflush(stdin);
827 		(void) fgets(ans, sizeof(ans) - 1, stdin);
828 		switch ((int) ans[0]) {
829 			case 'c':
830 			case 'C':
831 				ans[0] = 'C';
832 				ans[1] = '\0';
833 				zaction = kConfirmResumeProcSaidCancel;
834 				break;
835 			case 'o':
836 			case 'O':
837 				ans[0] = 'O';
838 				zaction = kConfirmResumeProcSaidOverwrite;
839 				break;
840 			case 'r':
841 			case 'R':
842 				if ((gConn.hasREST != kCommandAvailable) || (remotesize == kSizeUnknown)) {
843 					printf("\tResume is not available on this server.\n\n");
844 					ans[0] = '\0';
845 					break;
846 				} else if (remotesize < localsize) {
847 					printf("\tCannot resume when local file is already larger than the remote file.\n\n");
848 					ans[0] = '\0';
849 					break;
850 				} else if (remotesize <= localsize) {
851 					printf("\tLocal file is already the same size as the remote file.\n\n");
852 					ans[0] = '\0';
853 					break;
854 				}
855 				ans[0] = 'R';
856 				*startPoint = localsize;
857 				zaction = kConfirmResumeProcSaidResume;
858 				if (OneTimeMessage("auto-resume") != 0) {
859 					printf("\n\tNOTE: If you want NcFTP to guess automatically, \"set auto-resume yes\"\n\n");
860 				}
861 				break;
862 			case 's':
863 			case 'S':
864 				ans[0] = 'S';
865 				zaction = kConfirmResumeProcSaidSkip;
866 				break;
867 			case 'n':
868 			case 'N':
869 				ans[0] = 'N';
870 				ans[1] = '\0';
871 				zaction = kConfirmResumeProcSaidOverwrite;
872 				break;
873 			case 'a':
874 			case 'A':
875 				ans[0] = 'A';
876 				ans[1] = '\0';
877 				zaction = kConfirmResumeProcSaidAppend;
878 				break;
879 			case 'g':
880 			case 'G':
881 				ans[0] = 'G';
882 				zaction = kConfirmResumeProcSaidBestGuess;
883 				break;
884 			default:
885 				ans[0] = '\0';
886 		}
887 		if (ans[0] != '\0')
888 			break;
889 	}
890 
891 	if (ans[0] == 'N') {
892 		(void) memset(newname, 0, sizeof(newname));
893 		printf("\tSave as:  ");
894 		fflush(stdin);
895 		(void) fgets(newname, sizeof(newname) - 1, stdin);
896 		newname[strlen(newname) - 1] = '\0';
897 		if (newname[0] == '\0') {
898 			/* Nevermind. */
899 			printf("Skipped %s.\n", remotepath);
900 			zaction = kConfirmResumeProcSaidSkip;
901 		} else {
902 			*localpath = newname;
903 		}
904 	}
905 
906 	if (ans[1] == '!')
907 		gResumeAnswerAll = zaction;
908 	return (zaction);
909 }	/* NcFTPConfirmResumeDownloadProc */
910 
911 
912 
913 
914 /* Download files from the remote system. */
915 void
GetCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)916 GetCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
917 {
918 	int opt;
919 	int renameMode = 0;
920 	int recurseFlag = kRecursiveNo;
921 	int appendFlag = kAppendNo;
922 	int resumeFlag = kResumeYes;
923 	int tarflag = kTarYes;
924 	const char *dstdir = NULL;
925 	int rc;
926 	int i;
927 	int doGlob;
928 	int xtype = gBm.xferType;
929 	int nD = 0;
930 	int deleteFlag = kDeleteNo;
931 	char pattern[256];
932 	vsigproc_t osigint;
933 	ConfirmResumeDownloadProc confirmProc;
934 
935 	confirmProc = NcFTPConfirmResumeDownloadProc;
936 	gResumeAnswerAll = kConfirmResumeProcNotUsed;	/* Ask at least once each time */
937 	ARGSUSED(gUnusedArg);
938 	GetoptReset();
939 	while ((opt = Getopt(argc, argv, "aAzfrRTD")) >= 0) switch (opt) {
940 		case 'a':
941 			xtype = kTypeAscii;
942 			break;
943 		case 'A':
944 			/* Append to local files, instead of truncating
945 			 * them first.
946 			 */
947 			appendFlag = kAppendYes;
948 			break;
949 		case 'f':
950 		case 'Z':
951 			/* Do not try to resume a download, even if it
952 			 * appeared that some of the file was transferred
953 			 * already.
954 			 */
955 			resumeFlag = kResumeNo;
956 			confirmProc = NoConfirmResumeDownloadProc;
957 			break;
958 		case 'z':
959 			/* Special flag that lets you specify the
960 			 * destination file.  Normally a "get" will
961 			 * write the file by the same name as the
962 			 * remote file's basename.
963 			 */
964 			renameMode = 1;
965 			break;
966 		case 'r':
967 		case 'R':
968 			/* If the item is a directory, get the
969 			 * directory and all its contents.
970 			 */
971 			recurseFlag = kRecursiveYes;
972 			break;
973 		case 'T':
974 			/* If they said "-R", they may want to
975 			 * turn off TAR mode if they are trying
976 			 * to resume the whole directory.
977 			 * The disadvantage to TAR mode is that
978 			 * it always downloads the whole thing,
979 			 * which is why there is a flag to
980 			 * disable this.
981 			 */
982 			tarflag = kTarNo;
983 			break;
984 		case 'D':
985 			/* You can delete the remote file after
986 			 * you downloaded it successfully by using
987 			 * the -DD option.  It requires two -D's
988 			 * to minimize the odds of accidentally
989 			 * using a single -D.
990 			 */
991 			nD++;
992 			break;
993 		default:
994 			PrintCmdUsage(cmdp);
995 			return;
996 	}
997 
998 	if (nD >= 2)
999 		deleteFlag = kDeleteYes;
1000 
1001 	if (renameMode != 0) {
1002 		if (gOptInd > argc - 2) {
1003 			PrintCmdUsage(cmdp);
1004 			(void) fprintf(stderr, "\nFor get with rename, try \"get -z remote-path-name local-path-name\".\n");
1005 			return;
1006 		}
1007 		osigint = NcSignal(SIGINT, XferCanceller);
1008 		rc = FTPGetOneFile3(&gConn, argv[gOptInd], argv[gOptInd + 1], xtype, (-1), resumeFlag, appendFlag, deleteFlag, NoConfirmResumeDownloadProc, 0);
1009 		if (rc < 0)
1010 			FTPPerror(&gConn, rc, kErrCouldNotStartDataTransfer, "get", argv[gOptInd]);
1011 	} else {
1012 		osigint = NcSignal(SIGINT, XferCanceller);
1013 		for (i=gOptInd; i<argc; i++) {
1014 			doGlob = (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes;
1015 			STRNCPY(pattern, argv[i]);
1016 			StrRemoveTrailingSlashes(pattern);
1017 			rc = FTPGetFiles3(&gConn, pattern, dstdir, recurseFlag, doGlob, xtype, resumeFlag, appendFlag, deleteFlag, tarflag, confirmProc, 0);
1018 			if (rc < 0)
1019 				FTPPerror(&gConn, rc, kErrCouldNotStartDataTransfer, "get", argv[i]);
1020 		}
1021 	}
1022 	(void) NcSignal(SIGINT, osigint);
1023 	(void) fflush(stdin);
1024 
1025 	if (deleteFlag == kDeleteYes) {
1026 		/* Directory is now out of date */
1027 		FlushLsCache();
1028 	}
1029 }	/* GetCmd */
1030 
1031 
1032 
1033 
1034 /* Display some brief help for specified commands, or a list of commands. */
1035 void
HelpCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1036 HelpCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1037 {
1038 	CommandPtr c;
1039 	int showall = 0, helpall = 0;
1040 	int i, j, k, n;
1041 	int nRows, nCols;
1042 	int nCmds2Print;
1043 	int screenColumns;
1044 	int len, widestName;
1045 	char *cp, spec[16];
1046 	const char *cmdnames[64];
1047 
1048 	ARGSUSED(gUnusedArg);
1049 	assert(gNumCommands < (sizeof(cmdnames) / sizeof(char *)));
1050 	if (argc == 2) {
1051 		showall = (strcmp(argv[1], "showall") == 0);
1052 		helpall = (strcmp(argv[1], "helpall") == 0);
1053 	}
1054 	if (argc == 1 || showall) {
1055 		(void) printf("\
1056 Commands may be abbreviated.  'help showall' shows hidden and unsupported \n\
1057 commands.  'help <command>' gives a brief description of <command>.\n\n");
1058 
1059 		/* First, see how many commands we will be printing to the screen.
1060 		 * Unless 'showall' was given, we won't be printing the hidden
1061 		 * (i.e. not very useful to the end-user) commands.
1062 		 */
1063 		c = gCommands;
1064 		nCmds2Print = 0;
1065 		for (n = 0; n < (int) gNumCommands; c++, n++)
1066 			if ((!iscntrl((int) c->name[0])) && (!(c->flags & kCmdHidden) || showall))
1067 				nCmds2Print++;
1068 
1069 		(void) memset((char *) cmdnames, 0, sizeof(cmdnames));
1070 
1071 		/* Now form the list we'll be printing, and noting what the maximum
1072 		 * length of a command name was, so we can use that when determining
1073 		 * how to print in columns.
1074 		 */
1075 		c = gCommands;
1076 		i = 0;
1077 		widestName = 0;
1078 		for (n = 0; n < (int) gNumCommands; c++, n++) {
1079 			if ((!iscntrl((int) c->name[0])) && (!(c->flags & kCmdHidden) || showall)) {
1080 				cmdnames[i++] = c->name;
1081 				len = (int) strlen(c->name);
1082 				if (len > widestName)
1083 					widestName = len;
1084 			}
1085 		}
1086 
1087 		if ((cp = (char *) getenv("COLUMNS")) == NULL)
1088 			screenColumns = 80;
1089 		else
1090 			screenColumns = atoi(cp);
1091 
1092 		/* Leave an extra bit of whitespace for the margins between columns. */
1093 		widestName += 2;
1094 
1095 		nCols = (screenColumns + 0) / widestName;
1096 		nRows = nCmds2Print / nCols;
1097 		if ((nCmds2Print % nCols) > 0)
1098 			nRows++;
1099 
1100 		for (i = 0; i < nRows; i++) {
1101 			for (j = 0; j < nCols; j++) {
1102 				k = nRows * j + i;
1103 				if (k < nCmds2Print) {
1104 					(void) sprintf(spec, "%%-%ds",
1105 						(j < nCols - 1) ? widestName : widestName - 2
1106 					);
1107 					(void) printf(spec, cmdnames[k]);
1108 				}
1109 			}
1110 			(void) printf("\n");
1111 		}
1112 	} else if (helpall) {
1113 		/* Really intended for me, so I can debug the help strings. */
1114 		for (c = gCommands, n = 0; n < (int) gNumCommands; c++, n++) {
1115 			PrintCmdHelp(c);
1116 			PrintCmdUsage(c);
1117 		}
1118 	} else {
1119 		/* For each command name specified, print its help stuff. */
1120 		for (i=1; i<argc; i++) {
1121 			c = GetCommandByName(argv[i], 0);
1122 			if (c == kAmbiguousCommand) {
1123 				(void) printf("%s: ambiguous command name.\n", argv[i]);
1124 			} else if (c == kNoCommand) {
1125 				(void) printf("%s: no such command.\n", argv[i]);
1126 			} else {
1127 				if (i > 1)
1128 					(void) printf("\n");
1129 				PrintCmdHelp(c);
1130 				PrintCmdUsage(c);
1131 			}
1132 		}
1133 	}
1134 }	/* HelpCmd */
1135 
1136 
1137 
1138 
1139 /* Displays the list of saved bookmarks, so that the user can then choose
1140  * one by name.
1141  */
1142 static int
PrintHosts(void)1143 PrintHosts(void)
1144 {
1145 	FILE *bookmarks;
1146 	FILE *pager;
1147 	int nbm = 0;
1148 	Bookmark bm;
1149 	char url[128];
1150 #ifdef SIGPIPE
1151 	sigproc_t osigpipe;
1152 #endif
1153 
1154 	bookmarks = OpenBookmarkFile(NULL);
1155 	if (bookmarks == NULL)
1156 		return (0);
1157 
1158 #ifdef SIGPIPE
1159 	osigpipe = (sigproc_t) NcSignal(SIGPIPE, SIG_IGN);
1160 #endif
1161 	pager = OpenPager();
1162 
1163 	while (GetNextBookmark(bookmarks, &bm) == 0) {
1164 		nbm++;
1165 		if (nbm == 1) {
1166 			/* header */
1167 			(void) fprintf(pager, "--BOOKMARK----------URL--------------------------------------------------------\n");
1168 		}
1169 		BookmarkToURL(&bm, url, sizeof(url));
1170 		(void) fprintf(pager, "  %-16s  %s\n", bm.bookmarkName, url);
1171 	}
1172 
1173 	ClosePager(pager);
1174 	CloseBookmarkFile(bookmarks);
1175 
1176 #ifdef SIGPIPE
1177 	(void) NcSignal(SIGPIPE, osigpipe);
1178 #endif
1179 	return (nbm);
1180 }	/* PrintHosts */
1181 
1182 
1183 
1184 
1185 
1186 static int
RunBookmarkEditor(char * selectedBmName,size_t dsize)1187 RunBookmarkEditor(char *selectedBmName, size_t dsize)
1188 {
1189 #if defined(WIN32) || defined(_WINDOWS)
1190 	char ncftpbookmarks[260];
1191 	const char *prog;
1192 	int winExecResult;
1193 	HANDLE hMailSlot;
1194 	char msg[kNcFTPBookmarksMailslotMsgSize];
1195 	DWORD dwRead;
1196 	BOOL rc;
1197 
1198 	if (selectedBmName != NULL)
1199 		memset(selectedBmName, 0, dsize);
1200 	if (gOurInstallationPath[0] == '\0') {
1201 		(void) fprintf(stderr, "Cannot find path to %s.  Please re-run Setup.\n", "ncftpbookmarks.exe");
1202 		return (-1);
1203 	}
1204 	prog = ncftpbookmarks;
1205 	OurInstallationPath(ncftpbookmarks, sizeof(ncftpbookmarks), "ncftpbookmarks.exe");
1206 
1207 
1208 	hMailSlot = CreateMailslot(kNcFTPBookmarksMailslot, kNcFTPBookmarksMailslotMsgSize, MAILSLOT_WAIT_FOREVER, NULL);
1209 
1210 	if (hMailSlot == INVALID_HANDLE_VALUE) {
1211 		SysPerror("CreateMailslot");
1212 		(void) fprintf(stderr, "Could not create communication channel with %s.\n", "ncftpbookmarks.exe");
1213 		(void) fprintf(stderr, "%s", "This means if you select a bookmark to connect to that NcFTP\n");
1214 		(void) fprintf(stderr, "%s", "will not get the message from %s.\n");
1215 	}
1216 
1217 	winExecResult = WinExec(prog, SW_SHOWNORMAL);
1218 	if (winExecResult <= 31) switch (winExecResult) {
1219 		case ERROR_BAD_FORMAT:
1220 			fprintf(stderr, "Could not run %s: %s\n", prog, "The .EXE file is invalid");
1221 			return (-1);
1222 		case ERROR_FILE_NOT_FOUND:
1223 			fprintf(stderr, "Could not run %s: %s\n", prog, "The specified file was not found.");
1224 			return (-1);
1225 		case ERROR_PATH_NOT_FOUND:
1226 			fprintf(stderr, "Could not run %s: %s\n", prog, "The specified path was not found.");
1227 			return (-1);
1228 		default:
1229 			fprintf(stderr, "Could not run %s: Unknown error #%d.\n", prog, winExecResult);
1230 			return (-1);
1231 	}
1232 
1233 	if (hMailSlot != INVALID_HANDLE_VALUE) {
1234 		fprintf(stdout, "Waiting for %s to exit...\n", "ncftpbookmarks.exe");
1235 		ZeroMemory(msg, sizeof(msg));
1236 		dwRead = 0;
1237 		rc = ReadFile(
1238 			hMailSlot,
1239 			msg,
1240 			sizeof(msg),
1241 			&dwRead,
1242 			NULL
1243 			);
1244 
1245 		if (!rc) {
1246 			SysPerror("ReadFile");
1247 		} else {
1248 			msg[sizeof(msg) - 1] = '\0';
1249 			Strncpy(selectedBmName, msg, dsize);
1250 			Trace(0, "Selected bookmark from editor: [%s]\n", selectedBmName);
1251 		}
1252 		CloseHandle(hMailSlot);
1253 	}
1254 	return (0);
1255 
1256 #else
1257 #ifdef BINDIR
1258 	char ncftpbookmarks[256];
1259 	char *av[8];
1260 	int pid;
1261 	int status;
1262 	char bmSelectionFile[256];
1263 	char pidStr[32];
1264 	FILE *fp;
1265 
1266 	if (selectedBmName != NULL)
1267 		memset(selectedBmName, 0, dsize);
1268 	STRNCPY(ncftpbookmarks, BINDIR);
1269 	STRNCAT(ncftpbookmarks, "/");
1270 	STRNCAT(ncftpbookmarks, "ncftpbookmarks");
1271 
1272 	STRNCPY(bmSelectionFile, "view");
1273 	if ((selectedBmName != NULL) && (gOurDirectoryPath[0] != '\0')) {
1274 		sprintf(pidStr, ".%u", (unsigned int) getpid());
1275 		OurDirectoryPath(bmSelectionFile, sizeof(bmSelectionFile), kOpenSelectedBookmarkFileName);
1276 		STRNCAT(bmSelectionFile, pidStr);
1277 	}
1278 
1279 	if (access(ncftpbookmarks, X_OK) == 0) {
1280 		pid = (int) fork();
1281 		if (pid < 0) {
1282 			return (-1);
1283 		} else if (pid == 0) {
1284 			/* child */
1285 
1286 			av[0] = (char *) "ncftpbookmarks";
1287 			av[1] = bmSelectionFile;
1288 			av[2] = NULL;
1289 			execv(ncftpbookmarks, av);
1290 			exit(1);
1291 		} else {
1292 			/* parent NcFTP */
1293 			for (;;) {
1294 #ifdef HAVE_WAITPID
1295 				if ((waitpid(pid, &status, 0) < 0) && (errno != EINTR))
1296 					break;
1297 #else
1298 				if ((wait(&status) < 0) && (errno != EINTR))
1299 					break;
1300 #endif
1301 				if (WIFEXITED(status) || WIFSIGNALED(status))
1302 					break;		/* done */
1303 			}
1304 
1305 			if (strcmp(bmSelectionFile, "view") != 0) {
1306 				fp = fopen(bmSelectionFile, FOPEN_READ_TEXT);
1307 				if (fp != NULL) {
1308 					(void) FGets(selectedBmName, dsize, fp);
1309 					(void) fclose(fp);
1310 					(void) unlink(bmSelectionFile);
1311 					Trace(0, "Selected bookmark from editor: [%s]\n", selectedBmName);
1312 				}
1313 			}
1314 			return (0);
1315 		}
1316 	}
1317 	return (-1);
1318 #else	/* BINDIR */
1319 	/* Not installed. */
1320 	return (-1);
1321 #endif	/* BINDIR */
1322 #endif	/* Windows */
1323 }	/* RunBookmarkEditor */
1324 
1325 
1326 
1327 /* This just shows the list of saved bookmarks. */
1328 void
HostsCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1329 HostsCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1330 {
1331 	const char *av[3];
1332 	char bm[128];
1333 
1334 	ARGSUSED(gUnusedArg);
1335 	/* Skip visual mode if "-l". */
1336 	if ((argc == 1) && (RunBookmarkEditor(bm, sizeof(bm)) == 0)) {
1337 		if (bm[0] != '\0') {
1338 			av[0] = "open";
1339 			av[1] = bm;
1340 			av[2] = NULL;
1341 			OpenCmd(2, av, (CommandPtr) 0, (ArgvInfoPtr) 0);
1342 		}
1343 		return;
1344 	}
1345 	if (PrintHosts() <= 0) {
1346 		(void) printf("You haven't bookmarked any FTP sites.\n");
1347 		(void) printf("Before closing a site, you can use the \"bookmark\" command to save the current\nhost and directory for next time.\n");
1348 	} else {
1349 		(void) printf("\nTo use a bookmark, use the \"open\" command with the name of the bookmark.\n");
1350 	}
1351 }	/* HostsCmd */
1352 
1353 
1354 
1355 
1356 /* Show active background ncftp (ncftpbatch) jobs. */
1357 void
JobsCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1358 JobsCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1359 {
1360 	ARGSUSED(gUnusedArg);
1361 	Jobs();
1362 }	/* JobsCmd */
1363 
1364 
1365 
1366 
1367 /* Do a "ls" on the remote system and display the directory contents to our
1368  * user.  This command handles "dir" (ls -l), "ls", and "nlist" (ls -1).
1369  */
1370 void
ListCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1371 ListCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1372 {
1373 	volatile int i;
1374 	int j;
1375 	char options[32];
1376 	char option[2];
1377 	volatile int listmode;
1378 	FILE *stream;
1379 	volatile int paging;
1380 #if defined(WIN32) || defined(_WINDOWS)
1381 #else
1382 	int sj;
1383 	vsigproc_t osigpipe, osigint;
1384 #endif
1385 
1386 	ARGSUSED(gUnusedArg);
1387 	stream = stdout;
1388 	paging = 0;
1389 
1390 	if (argv[0][0] == 'd') {
1391 		/* dir */
1392 		listmode = 'l';
1393 	} else if (argv[0][0] == 'n') {
1394 		/* nlist */
1395 		listmode = '1';
1396 	} else if (argv[0][0] == 'p') {
1397 		/* pager */
1398 		paging = 1;
1399 
1400 		if (argv[0][1] == 'd') {
1401 			/* dir */
1402 			listmode = 'l';
1403 		} else if (argv[0][1] == 'n') {
1404 			/* nlist */
1405 			listmode = '1';
1406 		} else {
1407 			/* ls */
1408 			listmode = 'C';
1409 		}
1410 	} else {
1411 		/* ls */
1412 		listmode = 'C';
1413 	}
1414 	options[0] = '\0';
1415 	option[1] = '\0';
1416 
1417 	for (i=1; i<argc; i++) {
1418 		if (argv[i][0] != '-')
1419 			break;
1420 		if (argv[i][1] == '-') {
1421 			if (argv[i][2] == '\0') {
1422 				/* end of options. */
1423 				++i;
1424 				break;
1425 			} else {
1426 				/* GNU-esque long --option? */
1427 				PrintCmdUsage(cmdp);
1428 			}
1429 		} else {
1430 			for (j=1; ; j++) {
1431 				option[0] = argv[i][j];
1432 				if (argv[i][j] == '\0')
1433 					break;
1434 				switch (argv[i][j]) {
1435 					case 'l':
1436 						listmode = 'l';
1437 						break;
1438 					case '1':
1439 						listmode = '1';
1440 						break;
1441 					case 'C':
1442 						listmode = 'C';
1443 						break;
1444 					default:
1445 						(void) STRNCAT(options, option);
1446 						break;
1447 				}
1448 			}
1449 		}
1450 	}
1451 
1452 
1453 	if (paging != 0) {
1454 		stream = OpenPager();
1455 		if (stream == NULL) {
1456 			return;
1457 		}
1458 
1459 #if defined(WIN32) || defined(_WINDOWS)
1460 #elif defined(HAVE_SIGSETJMP)
1461 		osigpipe = osigint = (sigproc_t) 0;
1462 		sj = sigsetjmp(gCancelJmp, 1);
1463 #else	/* HAVE_SIGSETJMP */
1464 		osigpipe = osigint = (sigproc_t) 0;
1465 		sj = setjmp(gCancelJmp);
1466 #endif	/* HAVE_SIGSETJMP */
1467 
1468 #if defined(WIN32) || defined(_WINDOWS)
1469 #else
1470 		if (sj != 0) {
1471 			/* Caught a signal. */
1472 			(void) NcSignal(SIGPIPE, (FTPSigProc) SIG_IGN);
1473 			ClosePager(stream);
1474 			(void) NcSignal(SIGPIPE, osigpipe);
1475 			(void) NcSignal(SIGINT, osigint);
1476 			Trace(0, "Canceled because of signal %d.\n", gGotSig);
1477 			(void) fprintf(stderr, "Canceled.\n");
1478 			gMayCancelJmp = 0;
1479 			return;
1480 		} else {
1481 			osigpipe = NcSignal(SIGPIPE, Cancel);
1482 			osigint = NcSignal(SIGINT, Cancel);
1483 			gMayCancelJmp = 1;
1484 		}
1485 #endif
1486 	}
1487 
1488 	if (argv[i] == NULL) {
1489 		/* No directory specified, use cwd. */
1490 		Ls(NULL, listmode, options, stream);
1491 	} else {
1492 		/* List each item. */
1493 		for ( ; i<argc; i++) {
1494 			Ls(argv[i], listmode, options, stream);
1495 		}
1496 	}
1497 
1498 	if (paging != 0) {
1499 		ClosePager(stream);
1500 #if defined(WIN32) || defined(_WINDOWS)
1501 #else
1502 		(void) NcSignal(SIGPIPE, osigpipe);
1503 		(void) NcSignal(SIGINT, osigint);
1504 #endif
1505 	}
1506 	gMayCancelJmp = 0;
1507 }	/* ListCmd */
1508 
1509 
1510 
1511 
1512 /* Does a change of working directory on the local host. */
1513 void
LocalChdirCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1514 LocalChdirCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1515 {
1516 	int result;
1517 	const char *dir;
1518 	char buf[512];
1519 
1520 	ARGSUSED(gUnusedArg);
1521 	dir = ((argc < 2) ? gHome : argv[1]);
1522 	if ((dir[0] == '-') && (dir[1] == '\0')) {
1523 		if (gPrevLocalCWD[0] == '\0') {
1524 			(void) fprintf(stderr, "No previous local working directory to switch to.\n");
1525 			return;
1526 		} else {
1527 			dir = gPrevLocalCWD;
1528 		}
1529 	} else if (dir[0] == '~') {
1530 		if (dir[1] == '\0') {
1531 			dir = gHome;
1532 		} else if (dir[1] == '/') {
1533 			(void) STRNCPY(buf, gHome);
1534 			dir = STRNCAT(buf, dir + 1);
1535 		}
1536 	}
1537 	result = chdir(dir);
1538 	if (result < 0) {
1539 		perror(dir);
1540 	} else {
1541 		(void) STRNCPY(gPrevLocalCWD, gLocalCWD);
1542 		(void) FTPGetLocalCWD(gLocalCWD, sizeof(gLocalCWD));
1543 
1544 	}
1545 }	/* LocalChdirCmd */
1546 
1547 
1548 
1549 
1550 /* Does directory listing on the local host. */
1551 void
LocalListCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1552 LocalListCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1553 {
1554 #if defined(WIN32) || defined(_WINDOWS)
1555 	volatile int i;
1556 	int j;
1557 	char options[32];
1558 	char option[2];
1559 	volatile int listmode;
1560 	FILE *stream;
1561 	volatile int paging;
1562 
1563 
1564 	ARGSUSED(gUnusedArg);
1565 	stream = stdout;
1566 	paging = 0;
1567 
1568 	if (argv[0][1] == 'd') {
1569 		/* dir */
1570 		listmode = 'l';
1571 	} else if (argv[0][1] == 'n') {
1572 		/* nlist */
1573 		listmode = '1';
1574 	} else {
1575 		/* ls */
1576 		listmode = 'C';
1577 	}
1578 	options[0] = '\0';
1579 	option[1] = '\0';
1580 
1581 	for (i=1; i<argc; i++) {
1582 		if (argv[i][0] != '-')
1583 			break;
1584 		if (argv[i][1] == '-') {
1585 			if (argv[i][2] == '\0') {
1586 				/* end of options. */
1587 				++i;
1588 				break;
1589 			} else {
1590 				/* GNU-esque long --option? */
1591 				PrintCmdUsage(cmdp);
1592 			}
1593 		} else {
1594 			for (j=1; ; j++) {
1595 				option[0] = argv[i][j];
1596 				if (argv[i][j] == '\0')
1597 					break;
1598 				switch (argv[i][j]) {
1599 					case 'l':
1600 						listmode = 'l';
1601 						break;
1602 					case '1':
1603 						listmode = '1';
1604 						break;
1605 					case 'C':
1606 						listmode = 'C';
1607 						break;
1608 					default:
1609 						(void) STRNCAT(options, option);
1610 						break;
1611 				}
1612 			}
1613 		}
1614 	}
1615 
1616 
1617 	if (argv[i] == NULL) {
1618 		/* No directory specified, use cwd. */
1619 		LLs(NULL, listmode, options, stream);
1620 	} else {
1621 		/* List each item. */
1622 		for ( ; i<argc; i++) {
1623 			LLs(argv[i], listmode, options, stream);
1624 		}
1625 	}
1626 
1627 #else
1628 	FILE *volatile outfp;
1629 	FILE *volatile infp;
1630 	int i;
1631 	int sj;
1632 	int dashopts;
1633 	char incmd[256];
1634 	char line[256];
1635 	vsigproc_t osigpipe, osigint;
1636 
1637 	ARGSUSED(gUnusedArg);
1638 	(void) fflush(stdin);
1639 	outfp = OpenPager();
1640 
1641 	(void) STRNCPY(incmd, "/bin/ls");
1642 	for (i=1, dashopts=0; i<argc; i++) {
1643 		(void) STRNCAT(incmd, " ");
1644 		if (argv[i][0] == '-')
1645 			dashopts++;
1646 		(void) STRNCAT(incmd, argv[i]);
1647 	}
1648 
1649 	if (dashopts == 0) {
1650 		(void) STRNCPY(incmd, "/bin/ls -CF");
1651 		for (i=1; i<argc; i++) {
1652 			(void) STRNCAT(incmd, " ");
1653 			(void) STRNCAT(incmd, argv[i]);
1654 		}
1655 	}
1656 
1657 	infp = popen(incmd, "r");
1658 	if (infp == NULL) {
1659 		ClosePager(outfp);
1660 		return;
1661 	}
1662 
1663 
1664 #ifdef HAVE_SIGSETJMP
1665 	sj = sigsetjmp(gCancelJmp, 1);
1666 #else	/* HAVE_SIGSETJMP */
1667 	sj = setjmp(gCancelJmp);
1668 #endif	/* HAVE_SIGSETJMP */
1669 
1670 	if (sj != 0) {
1671 		/* Caught a signal. */
1672 		(void) NcSignal(SIGPIPE, (FTPSigProc) SIG_IGN);
1673 		ClosePager(outfp);
1674 		if (infp != NULL)
1675 			(void) pclose(infp);
1676 		(void) NcSignal(SIGPIPE, osigpipe);
1677 		(void) NcSignal(SIGINT, osigint);
1678 		(void) fprintf(stderr, "Canceled.\n");
1679 		Trace(0, "Canceled because of signal %d.\n", gGotSig);
1680 		gMayCancelJmp = 0;
1681 		return;
1682 	} else {
1683 		osigpipe = NcSignal(SIGPIPE, Cancel);
1684 		osigint = NcSignal(SIGINT, Cancel);
1685 		gMayCancelJmp = 1;
1686 	}
1687 
1688 	while (fgets(line, sizeof(line) - 1, infp) != NULL)
1689 		(void) fputs(line, outfp);
1690 	(void) fflush(outfp);
1691 
1692 	(void) pclose(infp);
1693 	infp = NULL;
1694 	ClosePager(outfp);
1695 	outfp = NULL;
1696 
1697 	(void) NcSignal(SIGPIPE, osigpipe);
1698 	(void) NcSignal(SIGINT, osigint);
1699 	gMayCancelJmp = 0;
1700 #endif
1701 }	/* LocalListCmd */
1702 
1703 
1704 
1705 
1706 /* static void
1707 Sys(const int argc, const char **const argv, const ArgvInfoPtr aip, const char *syscmd, int noDQuote)
1708 {
1709 	char cmd[256];
1710 	int i;
1711 
1712 	(void) STRNCPY(cmd, syscmd);
1713 	for (i = 1; i < argc; i++) {
1714 		if (aip->noglobargv[i] != 0) {
1715 			(void) STRNCAT(cmd, " '");
1716 			(void) STRNCAT(cmd, argv[i]);
1717 			(void) STRNCAT(cmd, "'");
1718 		} else if (noDQuote != 0) {
1719 			(void) STRNCAT(cmd, " ");
1720 			(void) STRNCAT(cmd, argv[i]);
1721 		} else {
1722 			(void) STRNCAT(cmd, " \"");
1723 			(void) STRNCAT(cmd, argv[i]);
1724 			(void) STRNCAT(cmd, "\" ");
1725 		}
1726 	}
1727 #if defined(WIN32) || defined(_WINDOWS)
1728 	fprintf(stderr, "Cannot run command: %s\n", cmd);
1729 #else
1730 	Trace(0, "Sys: %s\n", cmd);
1731 	(void) system(cmd);
1732 #endif
1733 }*/
1734 
1735 
1736 
1737 
1738 #if defined(WIN32) || defined(_WINDOWS)
1739 #else
1740 void
LocalChmodCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1741 LocalChmodCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1742 {
1743 	ARGSUSED(gUnusedArg);
1744 	Sys(argc, argv, aip, "/bin/chmod", 1);
1745 }	/* LocalChmodCmd */
1746 #endif
1747 
1748 
1749 
1750 void
LocalMkdirCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1751 LocalMkdirCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1752 {
1753 #if defined(WIN32) || defined(_WINDOWS)
1754 	const char *arg;
1755 	int i;
1756 
1757 	for (i = 1; i < argc; i++) {
1758 		arg = argv[i];
1759 		if (MkDirs(arg, 00755) < 0) {
1760 			perror(arg);
1761 		}
1762 	}
1763 #else
1764 	ARGSUSED(gUnusedArg);
1765 	Sys(argc, argv, aip, "/bin/mkdir", 0);
1766 #endif
1767 }	/* LocalMkdirCmd */
1768 
1769 
1770 
1771 
1772 #if defined(WIN32) || defined(_WINDOWS)
1773 #else
1774 void
LocalPageCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1775 LocalPageCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1776 {
1777 	ARGSUSED(gUnusedArg);
1778 	Sys(argc, argv, aip, gPager, 0);
1779 }	/* LocalPageCmd */
1780 #endif
1781 
1782 
1783 
1784 
1785 void
LocalRenameCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1786 LocalRenameCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1787 {
1788 #if defined(WIN32) || defined(_WINDOWS)
1789 	if (rename(argv[1], argv[2]) < 0) {
1790 		perror("rename");
1791 	}
1792 #else
1793 	ARGSUSED(gUnusedArg);
1794 	Sys(argc, argv, aip, "/bin/mv", 1);
1795 #endif
1796 }	/* LocalRenameCmd */
1797 
1798 
1799 
1800 
1801 void
LocalRmCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1802 LocalRmCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1803 {
1804 #if defined(WIN32) || defined(_WINDOWS)
1805 	int i;
1806 	int result;
1807 	LineList ll;
1808 	LinePtr lp;
1809 
1810 	ARGSUSED(gUnusedArg);
1811 	for (i=1; i<argc; i++) {
1812 		InitLineList(&ll);
1813 		result = FTPLocalGlob(&gConn, &ll, argv[i], (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
1814 		if (result < 0) {
1815 			FTPPerror(&gConn, result, kErrGlobFailed, "local glob", argv[i]);
1816 		} else {
1817 			for (lp = ll.first; lp != NULL; lp = lp->next) {
1818 				if (lp->line != NULL) {
1819 					if (remove(lp->line) < 0)
1820 						perror(lp->line);
1821 				}
1822 			}
1823 		}
1824 		DisposeLineListContents(&ll);
1825 	}
1826 #else
1827 	ARGSUSED(gUnusedArg);
1828 	Sys(argc, argv, aip, "/bin/rm", 1);
1829 #endif
1830 }	/* LocalRmCmd */
1831 
1832 
1833 
1834 
1835 void
LocalRmdirCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1836 LocalRmdirCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1837 {
1838 #if defined(WIN32) || defined(_WINDOWS)
1839 	int i;
1840 	int result;
1841 	LineList ll;
1842 	LinePtr lp;
1843 
1844 	ARGSUSED(gUnusedArg);
1845 	for (i=1; i<argc; i++) {
1846 		InitLineList(&ll);
1847 		result = FTPLocalGlob(&gConn, &ll, argv[i], (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
1848 		if (result < 0) {
1849 			FTPPerror(&gConn, result, kErrGlobFailed, "local glob", argv[i]);
1850 		} else {
1851 			for (lp = ll.first; lp != NULL; lp = lp->next) {
1852 				if (lp->line != NULL) {
1853 					if (rmdir(lp->line) < 0)
1854 						perror(lp->line);
1855 				}
1856 			}
1857 		}
1858 		DisposeLineListContents(&ll);
1859 	}
1860 #else
1861 	ARGSUSED(gUnusedArg);
1862 	Sys(argc, argv, aip, "/bin/rmdir", 1);
1863 #endif
1864 }	/* LocalRmdirCmd */
1865 
1866 
1867 
1868 
1869 /* Displays the current local working directory. */
1870 void
LocalPwdCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1871 LocalPwdCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1872 {
1873 	ARGSUSED(gUnusedArg);
1874 	if (FTPGetLocalCWD(gLocalCWD, sizeof(gLocalCWD)) != NULL) {
1875 		Trace(-1, "%s\n", gLocalCWD);
1876 	}
1877 }	/* LocalPwdCmd */
1878 
1879 
1880 
1881 
1882 /* This is a simple interface to name service.  I prefer using this instead
1883  * of nslookup.
1884  */
1885 void
LookupCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1886 LookupCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1887 {
1888 	int i, j;
1889 	struct hostent *hp;
1890 	const char *host;
1891 	char **cpp;
1892 	struct in_addr ip_address;
1893 	int shortMode, opt;
1894 	char ipStr[16];
1895 
1896 	ARGSUSED(gUnusedArg);
1897 	shortMode = 1;
1898 
1899 	GetoptReset();
1900 	while ((opt = Getopt(argc, argv, "v")) >= 0) {
1901 		if (opt == 'v')
1902 			shortMode = 0;
1903 		else {
1904 			PrintCmdUsage(cmdp);
1905 			return;
1906 		}
1907 	}
1908 
1909 	for (i=gOptInd; i<argc; i++) {
1910 		hp = GetHostEntry((host = argv[i]), &ip_address);
1911 		if ((i > gOptInd) && (shortMode == 0))
1912 			Trace(-1, "\n");
1913 		if (hp == NULL) {
1914 			Trace(-1, "Unable to get information about site %s.\n", host);
1915 		} else if (shortMode) {
1916 			MyInetAddr(ipStr, sizeof(ipStr), hp->h_addr_list, 0);
1917 			Trace(-1, "%-40s %s\n", hp->h_name, ipStr);
1918 		} else {
1919 			Trace(-1, "%s:\n", host);
1920 			Trace(-1, "    Name:     %s\n", hp->h_name);
1921 			for (cpp = hp->h_aliases; *cpp != NULL; cpp++)
1922 				Trace(-1, "    Alias:    %s\n", *cpp);
1923 			for (j = 0, cpp = hp->h_addr_list; *cpp != NULL; cpp++, ++j) {
1924 				MyInetAddr(ipStr, sizeof(ipStr), hp->h_addr_list, j);
1925 				Trace(-1, "    Address:  %s\n", ipStr);
1926 			}
1927 		}
1928 	}
1929 }	/* LookupCmd */
1930 
1931 
1932 
1933 /* Directory listing in a machine-readable format;
1934  * Mostly for debugging, since NcFTP uses MLSD automatically when it needs to.
1935  */
1936 void
MlsCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)1937 MlsCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
1938 {
1939 	int i;
1940 	int opt;
1941 	LinePtr linePtr, nextLinePtr;
1942 	int result;
1943 	LineList dirContents;
1944 	int mlsd = 1, x;
1945 	const char *item;
1946 
1947 	ARGSUSED(gUnusedArg);
1948 	GetoptReset();
1949 	while ((opt = Getopt(argc, argv, "dt")) >= 0) {
1950 		if ((opt == 'd') || (opt == 't')) {
1951 			/* Use MLST instead of MLSD,
1952 			 * which is similar to using "ls -d" instead of "ls".
1953 			 */
1954 			mlsd = 0;
1955 		} else {
1956 			PrintCmdUsage(cmdp);
1957 			return;
1958 		}
1959 	}
1960 
1961 	i = gOptInd;
1962 	if (i == argc) {
1963 		/* No args, do current directory. */
1964 		x = 1;
1965 		item = "";
1966 		if ((result = FTPListToMemory2(&gConn, item, &dirContents, (mlsd == 0) ? "-d" : "", 1, &x)) < 0) {
1967 			if (mlsd != 0) {
1968 				FTPPerror(&gConn, result, 0, "Could not MLSD", item);
1969 			} else {
1970 				FTPPerror(&gConn, result, 0, "Could not MLST", item);
1971 			}
1972 		} else {
1973 			for (linePtr = dirContents.first;
1974 				linePtr != NULL;
1975 				linePtr = nextLinePtr)
1976 			{
1977 				nextLinePtr = linePtr->next;
1978 				(void) fprintf(stdout, "%s\n", linePtr->line);
1979 				Trace(0, "%s\n", linePtr->line);
1980 			}
1981 		}
1982 	}
1983 
1984 	for ( ; i<argc; i++) {
1985 		x = 1;
1986 		item = argv[i];
1987 		if ((result = FTPListToMemory2(&gConn, item, &dirContents, (mlsd == 0) ? "-d" : "", 1, &x)) < 0) {
1988 			if (mlsd != 0) {
1989 				FTPPerror(&gConn, result, 0, "Could not MLSD", item);
1990 			} else {
1991 				FTPPerror(&gConn, result, 0, "Could not MLST", item);
1992 			}
1993 		} else {
1994 			for (linePtr = dirContents.first;
1995 				linePtr != NULL;
1996 				linePtr = nextLinePtr)
1997 			{
1998 				nextLinePtr = linePtr->next;
1999 				(void) fprintf(stdout, "%s\n", linePtr->line);
2000 				Trace(0, "%s\n", linePtr->line);
2001 			}
2002 		}
2003 	}
2004 }	/* MlsCmd */
2005 
2006 
2007 
2008 
2009 /* Create directories on the remote system. */
2010 void
MkdirCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)2011 MkdirCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
2012 {
2013 	int i;
2014 	int opt;
2015 	int result;
2016 	int recurseFlag = kRecursiveNo;
2017 
2018 	ARGSUSED(gUnusedArg);
2019 	GetoptReset();
2020 	while ((opt = Getopt(argc, argv, "p")) >= 0) {
2021 		if (opt == 'p') {
2022 			/* Try creating intermediate directories if they
2023 			 * don't exist.
2024 			 *
2025 			 * For example if only /pub/stuff existed, and you
2026 			 * do a "mkdir -p /pub/stuff/a/b/c", the "a" and "b"
2027 			 * directories would also be created.
2028 			 */
2029 			recurseFlag = kRecursiveYes;
2030 		} else {
2031 			PrintCmdUsage(cmdp);
2032 			return;
2033 		}
2034 	}
2035 
2036 	for (i=gOptInd; i<argc; i++) {
2037 		result = FTPMkdir(&gConn, argv[i], recurseFlag);
2038 		if (result < 0)
2039 			FTPPerror(&gConn, result, kErrMKDFailed, "Could not mkdir", argv[i]);
2040 	}
2041 
2042 	/* Really should just flush only the modified directories... */
2043 	FlushLsCache();
2044 }	/* MkdirCmd */
2045 
2046 
2047 
2048 /*VARARGS*/
2049 static void
OpenMsg(const char * const fmt,...)2050 OpenMsg(const char *const fmt, ...)
2051 {
2052 	va_list ap;
2053 	char buf[512];
2054 	size_t len, padlim;
2055 
2056 	padlim = (size_t) gScreenColumns;
2057 	if ((size_t) gScreenColumns > (sizeof(buf) - 1))
2058 		padlim = sizeof(buf) - 1;
2059 
2060 	va_start(ap, fmt);
2061 #ifdef HAVE_VSNPRINTF
2062 	len = (size_t) vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
2063 	va_end(ap);
2064 	buf[sizeof(buf) - 1] = '\0';
2065 #else
2066 	(void) vsprintf(buf, fmt, ap);
2067 	va_end(ap);
2068 	len = strlen(buf);
2069 #endif
2070 	buf[len] = '\0';
2071 	Trace(9, "%s\n", buf);
2072 	for (; len < padlim; len++)
2073 		buf[len] = ' ';
2074 	buf[len] = '\0';
2075 
2076 #if 0
2077 	(void) fprintf(stdout, "\r%s", buf);
2078 #else
2079 	(void) fprintf(stdout, "%s\r", buf);
2080 #endif
2081 	(void) fflush(stdout);
2082 }	/* OpenMsg */
2083 
2084 
2085 
2086 
2087 static void
NcFTPOpenPrintResponseProc(const FTPCIPtr cipUNUSED,ResponsePtr rp)2088 NcFTPOpenPrintResponseProc(const FTPCIPtr cipUNUSED, ResponsePtr rp)
2089 {
2090 	gUnusedArg = (cipUNUSED != NULL);
2091 	if ((rp->printMode & kResponseNoPrint) != 0)
2092 		return;
2093 #if 0
2094 	if (rp->code == 331)	/* Skip: "331 User name okay, need password." */
2095 		return;
2096 #else
2097 	/* This is only used to print errors. */
2098 	if (rp->code < 400)
2099 		return;
2100 #endif
2101 	PrintResp(&rp->msg);
2102 }	/* NcFTPOpenPrintResponseProc */
2103 
2104 
2105 
2106 static void
NcFTPOnConnectMessageProc(const FTPCIPtr cipUNUSED,ResponsePtr rp)2107 NcFTPOnConnectMessageProc(const FTPCIPtr cipUNUSED, ResponsePtr rp)
2108 {
2109 	gUnusedArg = (cipUNUSED != NULL);
2110 	(void) printf("\n");
2111 	PrintResp(&rp->msg);
2112 	OpenMsg("Logging in...");
2113 }	/* NcFTPOnConnectMessageProc */
2114 
2115 
2116 
2117 static void
NcFTPOnLoginMessageProc(const FTPCIPtr cipUNUSED,ResponsePtr rp)2118 NcFTPOnLoginMessageProc(const FTPCIPtr cipUNUSED, ResponsePtr rp)
2119 {
2120 	gUnusedArg = (cipUNUSED != NULL);
2121 	(void) printf("\n");
2122 	PrintResp(&rp->msg);
2123 	OpenMsg("Logging in...");
2124 }	/* NcFTPOnLoginMessageProc */
2125 
2126 
2127 
2128 
2129 static void
NcFTPRedialStatusProc(const FTPCIPtr cipUNUSED,int mode,int val)2130 NcFTPRedialStatusProc(const FTPCIPtr cipUNUSED, int mode, int val)
2131 {
2132 	gUnusedArg = (cipUNUSED != NULL);
2133 	if (mode == kRedialStatusDialing) {
2134 		if (val > 0) {
2135 			OpenMsg("Redialing (try %d)...", val);
2136 			sleep(1);	/* Give time for message to stay */
2137 		}
2138 	} else if (mode == kRedialStatusSleeping) {
2139 		OpenMsg("Sleeping %d seconds...", val);
2140 	}
2141 }	/* NcFTPRedialStatusProc */
2142 
2143 
2144 
2145 
2146 static void
NcFTPGetPassphraseProc(const FTPCIPtr cip,LineListPtr pwPrompt,char * pass,size_t dsize)2147 NcFTPGetPassphraseProc(const FTPCIPtr cip, LineListPtr pwPrompt, char *pass, size_t dsize)
2148 {
2149 	LinePtr lp;
2150 
2151 	(void) printf("\nPassword requested by %s for user \"%s\".\n\n",
2152 		cip->host,
2153 		cip->user
2154 		);
2155 
2156 	for (lp = pwPrompt->first; lp != NULL; lp = lp->next) {
2157 		(void) printf("    %s\n", lp->line);
2158 	}
2159 	(void) printf("\n");
2160 	(void) gl_getpass("Password: ", pass, (int) dsize);
2161 }	/* NcFTPGetPassphraseProc */
2162 
2163 
2164 
2165 
2166 /* Attempts to establish a new FTP connection to a remote host. */
2167 int
DoOpen(void)2168 DoOpen(void)
2169 {
2170 	int result;
2171 	char ipstr[128];
2172 	char ohost[128];
2173 #ifdef SIGALRM
2174 	sigproc_t osigalrm;
2175 #endif
2176 	char prompt[256];
2177 
2178 	if (gConn.firewallType == kFirewallNotInUse) {
2179 		(void) STRNCPY(ohost, gConn.host);
2180 		OpenMsg("Resolving %s...", ohost);
2181 		if ((gLoadedBm != 0) && (gBm.lastIP[0] != '\0')) {
2182 			result = GetHostByName(ipstr, sizeof(ipstr), ohost, 3);
2183 			if (result < 0) {
2184 				(void) STRNCPY(ipstr, gBm.lastIP);
2185 				result = 0;
2186 			} else {
2187 				result = GetHostByName(ipstr, sizeof(ipstr), ohost, 7);
2188 			}
2189 		} else {
2190 			result = GetHostByName(ipstr, sizeof(ipstr), ohost, 10);
2191 		}
2192 		if (result < 0) {
2193 			(void) printf("\n");
2194 			(void) printf("Unknown host \"%s\".\n", ohost);
2195 			return (-1);
2196 		}
2197 		(void) STRNCPY(gConn.host, ipstr);
2198 		OpenMsg("Connecting to %s...", ipstr);
2199 	} else {
2200 		OpenMsg("Connecting to %s via %s...", gConn.host, gConn.firewallHost);
2201 		Trace(0, "Fw: %s  Type: %d  User: %s  Pass: %s  Port: %u\n",
2202 			gConn.firewallHost,
2203 			gConn.firewallType,
2204 			gConn.firewallUser,
2205 			(gConn.firewallPass[0] == '\0') ? "(none)" : "********",
2206 			gConn.firewallPort
2207 		);
2208 		Trace(0, "FwExceptions: %s\n", gFirewallExceptionList);
2209 		if (strchr(gLib.ourHostName, '.') == NULL) {
2210 			Trace(0, "NOTE:  Your domain name could not be detected.\n");
2211 			if (gConn.firewallType != kFirewallNotInUse) {
2212 				Trace(0, "       Make sure you manually add your domain name to firewall-exception-list.\n");
2213 			}
2214 		}
2215 	}
2216 
2217 	if (gConn.firewallPass[0] == '\0') {
2218 		switch (gConn.firewallType) {
2219 			case kFirewallNotInUse:
2220 				break;
2221 			case kFirewallUserAtSite:
2222 				break;
2223 			case kFirewallLoginThenUserAtSite:
2224 			case kFirewallSiteSite:
2225 			case kFirewallOpenSite:
2226 			case kFirewallUserAtUserPassAtPass:
2227 			case kFirewallFwuAtSiteFwpUserPass:
2228 			case kFirewallUserAtSiteFwuPassFwp:
2229 				(void) printf("\n");
2230 				(void) STRNCPY(prompt, "Password for firewall user \"");
2231 				(void) STRNCAT(prompt, gConn.firewallUser);
2232 				(void) STRNCAT(prompt, "\" at ");
2233 				(void) STRNCAT(prompt, gConn.firewallHost);
2234 				(void) STRNCAT(prompt, ": ");
2235 				(void) gl_getpass(prompt, gConn.firewallPass, sizeof(gConn.firewallPass));
2236 				break;
2237 		}
2238 	}
2239 
2240 	if ((gConn.user[0] != '\0') && (strcmp(gConn.user, "anonymous") != 0) && (strcmp(gConn.user, "ftp") != 0)) {
2241 		gConn.passphraseProc = NcFTPGetPassphraseProc;
2242 	}
2243 
2244 	/* Register our callbacks. */
2245 	gConn.printResponseProc = NcFTPOpenPrintResponseProc;
2246 	gConn.onConnectMsgProc = NcFTPOnConnectMessageProc;
2247 	gConn.onLoginMsgProc = NcFTPOnLoginMessageProc;
2248 	gConn.redialStatusProc = NcFTPRedialStatusProc;
2249 
2250 #ifdef SIGALRM
2251 	osigalrm = NcSignal(SIGALRM, (FTPSigProc) SIG_IGN);
2252 	result = FTPOpenHost(&gConn);
2253 	(void) NcSignal(SIGALRM, osigalrm);
2254 #else
2255 	result = FTPOpenHost(&gConn);
2256 #endif
2257 
2258 	if (gConn.firewallType == kFirewallNotInUse)
2259 		(void) STRNCPY(gConn.host, ohost);		/* Put it back. */
2260 	if (result >= 0) {
2261 		(void) time(&gBm.lastCall);
2262 		LogOpen(gConn.host);
2263 		OpenMsg("Logged in to %s.", gConn.host);
2264 		(void) printf("\n");
2265 
2266 		/* Remove callback. */
2267 		gConn.printResponseProc = 0;
2268 
2269 		/* Need to note what our "root" was before we change it. */
2270 		if (gConn.startingWorkingDirectory == NULL) {
2271 			(void) STRNCPY(gRemoteCWD, "/");	/* Guess! */
2272 		} else {
2273 			(void) STRNCPY(gRemoteCWD, gConn.startingWorkingDirectory);
2274 		}
2275 		(void) STRNCPY(gStartDir, gRemoteCWD);
2276 
2277 		/* If the bookmark specified a remote directory, change to it now. */
2278 		if ((gLoadedBm != 0) && (gBm.dir[0] != '\0')) {
2279 			result = Chdirs(&gConn, gBm.dir);
2280 			if (result < 0) {
2281 				FTPPerror(&gConn, result, kErrCWDFailed, "Could not chdir to previous directory", gBm.dir);
2282 			}
2283 			Trace(-1, "Current remote directory is %s.\n", gRemoteCWD);
2284 		}
2285 
2286 		/* If the bookmark specified a local directory, change to it now. */
2287 		if ((gLoadedBm != 0) && (gBm.ldir[0] != '\0')) {
2288 			(void) chdir(gBm.ldir);
2289 			(void) STRNCPY(gPrevLocalCWD, gLocalCWD);
2290 			if (FTPGetLocalCWD(gLocalCWD, sizeof(gLocalCWD)) != NULL) {
2291 				Trace(-1, "Current local directory is %s.\n", gLocalCWD);
2292 			}
2293 		}
2294 
2295 		/* Identify the FTP client type to the server.  Most don't understand this yet. */
2296 		if (gConn.hasCLNT != kCommandNotAvailable)
2297 			(void) FTPCmd(&gConn, "CLNT NcFTP %.5s %s", gVersion + 11, gOS);
2298 		return (0);
2299 	} else {
2300 		FTPPerror(&gConn, result, 0, "Could not open host", gConn.host);
2301 	}
2302 
2303 	/* Remove callback. */
2304 	gConn.printResponseProc = 0;
2305 	(void) printf("\n");
2306 	return (-1);
2307 }	/* Open */
2308 
2309 
2310 
2311 
2312 /* Chooses a new remote system to connect to, and attempts to establish
2313  * a new FTP connection.  This function is in charge of collecting the
2314  * information needed to do the open, and then doing it.
2315  */
2316 void
OpenCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)2317 OpenCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
2318 {
2319 	int c;
2320 	int opts = 0;
2321 	int uOptInd = 0;
2322 	int n;
2323 	int rc;
2324 	char url[256];
2325 	char urlfile[128];
2326 	int directoryURL = 0;
2327 	LineList cdlist;
2328 	LinePtr lp;
2329 	char prompt[256];
2330 
2331 	ARGSUSED(gUnusedArg);
2332 	FlushLsCache();
2333 	CloseHost();
2334 	gLoadedBm = 0;
2335 	InitConnectionInfo();
2336 
2337 	/* Need to find the host argument first. */
2338 	GetoptReset();
2339 	while ((c = Getopt(argc, argv, "aP:u:p:J:rd:g:")) > 0) switch(c) {
2340 		case 'u':
2341 			uOptInd = gOptInd + 1;
2342 			opts++;
2343 			break;
2344 		default:
2345 			opts++;
2346 	}
2347 	if (gOptInd < argc) {
2348 		(void) STRNCPY(gConn.host, argv[gOptInd]);
2349 		(void) STRNCPY(url, argv[gOptInd]);
2350 	} else if (uOptInd > argc) {
2351 		/* Special hack for v2.4.2 compatibility */
2352 		(void) STRNCPY(gConn.host, argv[argc - 1]);
2353 		(void) STRNCPY(url, argv[argc - 1]);
2354 	} else {
2355 		/* No host arg */
2356 		if (opts > 0) {
2357 			PrintCmdUsage(cmdp);
2358 		} else if (RunBookmarkEditor(gConn.host, sizeof(gConn.host)) == 0) {
2359 			if (gConn.host[0] != '\0') {
2360 				gLoadedBm = 1;
2361 				/* okay, now fall through */
2362 			} else {
2363 				return;
2364 			}
2365 		} else if (PrintHosts() <= 0) {
2366 			(void) printf("You haven't bookmarked any FTP sites.\n");
2367 			(void) printf("Before closing a site, you can use the \"bookmark\" command to save the current\nhost and directory for next time.\n");
2368 			return;
2369 		} else {
2370 			(void) printf("\nTo use a bookmark, use the \"open\" command with the name of the bookmark.\n");
2371 			return;
2372 		}
2373 	}
2374 
2375 	InitLineList(&cdlist);
2376 
2377 	if (GetBookmark(gConn.host, &gBm) >= 0) {
2378 		gLoadedBm = 1;
2379 		(void) STRNCPY(gConn.host, gBm.name);
2380 		(void) STRNCPY(gConn.user, gBm.user);
2381 		(void) STRNCPY(gConn.pass, gBm.pass);
2382 		(void) STRNCPY(gConn.acct, gBm.acct);
2383 		gConn.hasSIZE = gBm.hasSIZE;
2384 		gConn.hasMDTM = gBm.hasMDTM;
2385 		gConn.hasUTIME = gBm.hasUTIME;
2386 		gConn.port = gBm.port;
2387 
2388 		/* Note:  Version 3 only goes off of the
2389 		 * global "gDataPortMode" setting instead of
2390 		 * setting the dataPortMode on a per-site
2391 		 * basis.
2392 		 */
2393 		gConn.hasPASV = gBm.hasPASV;
2394 	} else {
2395 		SetBookmarkDefaults(&gBm);
2396 
2397 		memcpy(&gTmpURLConn, &gConn, sizeof(gTmpURLConn));
2398 		rc = DecodeDirectoryURL(&gTmpURLConn, url, &cdlist, urlfile, sizeof(urlfile));
2399 		if (rc == kMalformedURL) {
2400 			(void) fprintf(stdout, "Malformed URL: %s\n", url);
2401 			DisposeLineListContents(&cdlist);
2402 			return;
2403 		} else if (rc == kNotURL) {
2404 			directoryURL = 0;
2405 		} else {
2406 			/* It was a URL. */
2407 			if (urlfile[0] != '\0') {
2408 				/* It was obviously not a directory */
2409 				(void) fprintf(stdout, "Use ncftpget or ncftpput to handle file URLs.\n");
2410 				DisposeLineListContents(&cdlist);
2411 				return;
2412 			}
2413 			memcpy(&gConn, &gTmpURLConn, sizeof(gConn));
2414 			directoryURL = 1;
2415 		}
2416 	}
2417 
2418 	if (MayUseFirewall(gConn.host, gFirewallType, gFirewallExceptionList) != 0) {
2419 		gConn.firewallType = gFirewallType;
2420 		(void) STRNCPY(gConn.firewallHost, gFirewallHost);
2421 		(void) STRNCPY(gConn.firewallUser, gFirewallUser);
2422 		(void) STRNCPY(gConn.firewallPass, gFirewallPass);
2423 		gConn.firewallPort = gFirewallPort;
2424 	}
2425 
2426 	GetoptReset();
2427 	while ((c = Getopt(argc, argv, "aP:u:p:J:j:rd:g:")) > 0) switch(c) {
2428 		case 'J':
2429 		case 'j':
2430 			(void) STRNCPY(gConn.acct, gOptArg);
2431 			break;
2432 		case 'a':
2433 			(void) STRNCPY(gConn.user, "anonymous");
2434 			(void) STRNCPY(gConn.pass, "");
2435 			(void) STRNCPY(gConn.acct, "");
2436 			break;
2437 		case 'P':
2438 			gConn.port = atoi(gOptArg);
2439 			break;
2440 		case 'u':
2441 			if (uOptInd <= argc)
2442 				(void) STRNCPY(gConn.user, gOptArg);
2443 			break;
2444 		case 'p':
2445 			(void) STRNCPY(gConn.pass, gOptArg);	/* Don't recommend doing this! */
2446 			break;
2447 		case 'r':
2448 			/* redial is on by default */
2449 			break;
2450 		case 'g':
2451 			n = atoi(gOptArg);
2452 			gConn.maxDials = n;
2453 			break;
2454 		case 'd':
2455 			n = atoi(gOptArg);
2456 			if (n >= 10)
2457 				gConn.redialDelay = n;
2458 			break;
2459 		default:
2460 			PrintCmdUsage(cmdp);
2461 			DisposeLineListContents(&cdlist);
2462 			return;
2463 	}
2464 
2465 	if (uOptInd > argc) {
2466 		(void) STRNCPY(prompt, "Username at ");
2467 		(void) STRNCAT(prompt, gConn.host);
2468 		(void) STRNCAT(prompt, ": ");
2469 		(void) gl_getpass(prompt, gConn.user, sizeof(gConn.user));
2470 	}
2471 
2472 	rc = DoOpen();
2473 	if ((rc >= 0) && (directoryURL != 0)) {
2474 		for (lp = cdlist.first; lp != NULL; lp = lp->next) {
2475 			rc = FTPChdir(&gConn, lp->line);
2476 			if (rc != kNoErr) {
2477 				FTPPerror(&gConn, rc, kErrCWDFailed, "Could not chdir to", lp->line);
2478 				break;
2479 			}
2480 		}
2481 		rc = FTPGetCWD(&gConn, gRemoteCWD, sizeof(gRemoteCWD));
2482 		if (rc != kNoErr) {
2483 			FTPPerror(&gConn, rc, kErrPWDFailed, NULL, NULL);
2484 		} else {
2485 			(void) printf("Current remote directory is %s.\n", gRemoteCWD);
2486 		}
2487 	}
2488 	DisposeLineListContents(&cdlist);
2489 }	/* OpenCmd */
2490 
2491 
2492 
2493 
2494 /* View a remote file through the users $PAGER. */
2495 void
PageCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)2496 PageCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
2497 {
2498 	int result;
2499 	int i;
2500 	FILE *volatile stream;
2501 #if defined(WIN32) || defined(_WINDOWS)
2502 #else
2503 	int sj;
2504 	vsigproc_t osigpipe, osigint;
2505 #endif
2506 
2507 	ARGSUSED(gUnusedArg);
2508 	stream = OpenPager();
2509 	if (stream == NULL) {
2510 		return;
2511 	}
2512 
2513 #if defined(WIN32) || defined(_WINDOWS)
2514 #else
2515 
2516 #ifdef HAVE_SIGSETJMP
2517 	osigpipe = osigint = (sigproc_t) 0;
2518 	sj = sigsetjmp(gCancelJmp, 1);
2519 #else	/* HAVE_SIGSETJMP */
2520 	osigpipe = osigint = (sigproc_t) 0;
2521 	sj = setjmp(gCancelJmp);
2522 #endif	/* HAVE_SIGSETJMP */
2523 
2524 	if (sj != 0) {
2525 		/* Caught a signal. */
2526 		(void) NcSignal(SIGPIPE, (FTPSigProc) SIG_IGN);
2527 		ClosePager(stream);
2528 		(void) NcSignal(SIGPIPE, osigpipe);
2529 		(void) NcSignal(SIGINT, osigint);
2530 		(void) fprintf(stderr, "Canceled.\n");
2531 		Trace(0, "Canceled because of signal %d.\n", gGotSig);
2532 		gMayCancelJmp = 0;
2533 		return;
2534 	} else {
2535 		osigpipe = NcSignal(SIGPIPE, Cancel);
2536 		osigint = NcSignal(SIGINT, Cancel);
2537 		gMayCancelJmp = 1;
2538 	}
2539 
2540 #endif
2541 
2542 	for (i=1; i<argc; i++) {
2543 		result = FTPGetOneFile2(&gConn, argv[i], NULL, kTypeAscii, _fileno(stream), kResumeNo, kAppendNo);
2544 		if (result < 0) {
2545 			if (errno != EPIPE) {
2546 				ClosePager(stream);
2547 				stream = NULL;
2548 				FTPPerror(&gConn, result, kErrCouldNotStartDataTransfer, argv[0], argv[i]);
2549 			}
2550 			break;
2551 		}
2552 	}
2553 
2554 #if defined(WIN32) || defined(_WINDOWS)
2555 	ClosePager(stream);
2556 #else
2557 	(void) NcSignal(SIGPIPE, (FTPSigProc) SIG_IGN);
2558 	ClosePager(stream);
2559 	(void) NcSignal(SIGPIPE, osigpipe);
2560 	(void) NcSignal(SIGINT, osigint);
2561 #endif
2562 	gMayCancelJmp = 0;
2563 }	/* PageCmd */
2564 
2565 
2566 
2567 
2568 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)2569 NcFTPConfirmResumeUploadProc(
2570 	const char *volatile localpath,
2571 	volatile longest_int localsize,
2572 	volatile time_t localmtime,
2573 	const char *volatile *remotepath,
2574 	volatile longest_int remotesize,
2575 	volatile time_t remotemtime,
2576 	volatile longest_int *volatile startPoint)
2577 {
2578 	int zaction = kConfirmResumeProcSaidBestGuess;
2579 	char tstr[80], ans[32];
2580 	static char newname[128];	/* arrggh... static. */
2581 
2582 	if (gResumeAnswerAll != kConfirmResumeProcNotUsed)
2583 		return (gResumeAnswerAll);
2584 
2585 	if (gAutoResume != 0)
2586 		return (kConfirmResumeProcSaidBestGuess);
2587 
2588 	printf("\nThe remote file \"%s\" already exists.\n", *remotepath);
2589 
2590 	if ((localmtime != kModTimeUnknown) && (localsize != kSizeUnknown)) {
2591 		(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &localmtime));
2592 		(void) printf(
2593 #if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
2594 			"\tLocal:  %12lld bytes, dated %s.\n",
2595 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
2596 			"\tLocal:  %12qd bytes, dated %s.\n",
2597 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
2598 			"\tLocal:  %12I64d bytes, dated %s.\n",
2599 #else
2600 			"\tLocal:  %12ld bytes, dated %s.\n",
2601 #endif
2602 			localsize,
2603 			tstr
2604 		);
2605 		if ((remotemtime == localmtime) && (remotesize == localsize)) {
2606 			(void) printf("\t(Files are identical, skipped)\n\n");
2607 			return (kConfirmResumeProcSaidSkip);
2608 		}
2609 	} else if (localsize != kSizeUnknown) {
2610 		(void) printf(
2611 #if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
2612 			"\tLocal:  %12lld bytes, date unknown.\n",
2613 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
2614 			"\tLocal:  %12qd bytes, date unknown.\n",
2615 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
2616 			"\tLocal:  %12I64d bytes, date unknown.\n",
2617 #else
2618 			"\tLocal:  %12ld bytes, date unknown.\n",
2619 #endif
2620 			localsize
2621 		);
2622 	} else if (localmtime != kModTimeUnknown) {
2623 		(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &localmtime));
2624 		(void) printf(
2625 			"\tLocal:  size unknown, dated %s.\n",
2626 			tstr
2627 		);
2628 	}
2629 
2630 	tstr[sizeof(tstr) - 1] = '\0';
2631 	if ((remotemtime != kModTimeUnknown) && (remotesize != kSizeUnknown)) {
2632 		(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &remotemtime));
2633 		(void) printf(
2634 #if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
2635 			"\tRemote: %12lld bytes, dated %s.\n",
2636 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
2637 			"\tRemote: %12qd bytes, dated %s.\n",
2638 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
2639 			"\tRemote: %12I64d bytes, dated %s.\n",
2640 #else
2641 			"\tRemote: %12ld bytes, dated %s.\n",
2642 #endif
2643 			remotesize,
2644 			tstr
2645 		);
2646 	} else if (remotesize != kSizeUnknown) {
2647 		(void) printf(
2648 #if defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_LLD)
2649 			"\tRemote: %12lld bytes, date unknown.\n",
2650 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_QD)
2651 			"\tRemote: %12qd bytes, date unknown.\n",
2652 #elif defined(HAVE_LONG_LONG) && defined(PRINTF_LONG_LONG_I64D)
2653 			"\tRemote: %12I64d bytes, date unknown.\n",
2654 #else
2655 			"\tRemote: %12ld bytes, date unknown.\n",
2656 #endif
2657 			remotesize
2658 		);
2659 	} else if (remotemtime != kModTimeUnknown) {
2660 		(void) strftime(tstr, sizeof(tstr) - 1, "%c", localtime((time_t *) &remotemtime));
2661 		(void) printf(
2662 			"\tRemote: size unknown, dated %s.\n",
2663 			tstr
2664 		);
2665 	}
2666 
2667 	printf("\n");
2668 	fflush(stdin);
2669 	(void) memset(ans, 0, sizeof(ans));
2670 	for (;;) {
2671 		(void) printf("\t[O]verwrite?");
2672 		if ((gConn.hasREST == kCommandAvailable) && (remotesize < localsize))
2673 			printf("  [R]esume?");
2674 		printf("  [A]ppend to?  [S]kip?  [N]ew Name?\n");
2675 		(void) printf("\t[O!]verwrite all?");
2676 		if ((gConn.hasREST == kCommandAvailable) && (remotesize < localsize))
2677 			printf("  [R!]esume all?");
2678 		printf("  [S!]kip all?  [C]ancel  > ");
2679 		(void) fgets(ans, sizeof(ans) - 1, stdin);
2680 		switch ((int) ans[0]) {
2681 			case 'c':
2682 			case 'C':
2683 				ans[0] = 'C';
2684 				ans[1] = '\0';
2685 				zaction = kConfirmResumeProcSaidCancel;
2686 				break;
2687 			case 'o':
2688 			case 'O':
2689 				ans[0] = 'O';
2690 				zaction = kConfirmResumeProcSaidOverwrite;
2691 				break;
2692 			case 'r':
2693 			case 'R':
2694 				if (gConn.hasREST != kCommandAvailable) {
2695 					printf("\tResume is not available on this server.\n\n");
2696 					ans[0] = '\0';
2697 					break;
2698 				} else if (remotesize > localsize) {
2699 					printf("\tCannot resume when remote file is already larger than the local file.\n\n");
2700 					ans[0] = '\0';
2701 					break;
2702 				} else if (remotesize == localsize) {
2703 					printf("\tRemote file is already the same size as the local file.\n\n");
2704 					ans[0] = '\0';
2705 					break;
2706 				}
2707 				ans[0] = 'R';
2708 				*startPoint = remotesize;
2709 				zaction = kConfirmResumeProcSaidResume;
2710 				if (OneTimeMessage("auto-resume") != 0) {
2711 					printf("\n\tNOTE: If you want NcFTP to guess automatically, \"set auto-resume yes\"\n\n");
2712 				}
2713 				break;
2714 			case 's':
2715 			case 'S':
2716 				ans[0] = 'S';
2717 				zaction = kConfirmResumeProcSaidSkip;
2718 				break;
2719 			case 'n':
2720 			case 'N':
2721 				ans[0] = 'N';
2722 				ans[1] = '\0';
2723 				zaction = kConfirmResumeProcSaidOverwrite;
2724 				break;
2725 			case 'a':
2726 			case 'A':
2727 				ans[0] = 'A';
2728 				ans[1] = '\0';
2729 				zaction = kConfirmResumeProcSaidAppend;
2730 				break;
2731 			case 'g':
2732 			case 'G':
2733 				ans[0] = 'G';
2734 				zaction = kConfirmResumeProcSaidBestGuess;
2735 				break;
2736 			default:
2737 				ans[0] = '\0';
2738 		}
2739 		if (ans[0] != '\0')
2740 			break;
2741 	}
2742 
2743 	if (ans[0] == 'N') {
2744 		(void) memset(newname, 0, sizeof(newname));
2745 		printf("\tSave as:  ");
2746 		fflush(stdin);
2747 		(void) fgets(newname, sizeof(newname) - 1, stdin);
2748 		newname[strlen(newname) - 1] = '\0';
2749 		if (newname[0] == '\0') {
2750 			/* Nevermind. */
2751 			printf("Skipped %s.\n", localpath);
2752 			zaction = kConfirmResumeProcSaidSkip;
2753 		} else {
2754 			*remotepath = newname;
2755 		}
2756 	}
2757 
2758 	if (ans[1] == '!')
2759 		gResumeAnswerAll = zaction;
2760 	return (zaction);
2761 }	/* NcFTPConfirmResumeUploadProc */
2762 
2763 
2764 
2765 
2766 /* Upload files to the remote system, permissions permitting. */
2767 void
PutCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)2768 PutCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
2769 {
2770 	int opt;
2771 	int renameMode = 0;
2772 	int recurseFlag = kRecursiveNo;
2773 	int appendFlag = kAppendNo;
2774 	const char *dstdir = NULL;
2775 	int rc;
2776 	int i;
2777 	int doGlob;
2778 	int xtype = gBm.xferType;
2779 	int nD = 0;
2780 	int deleteFlag = kDeleteNo;
2781 	int resumeFlag = kResumeYes;
2782 	char pattern[256];
2783 	vsigproc_t osigint;
2784 	ConfirmResumeUploadProc confirmProc;
2785 
2786 	confirmProc = NcFTPConfirmResumeUploadProc;
2787 	gResumeAnswerAll = kConfirmResumeProcNotUsed;	/* Ask at least once each time */
2788 	ARGSUSED(gUnusedArg);
2789 	GetoptReset();
2790 	while ((opt = Getopt(argc, argv, "AafZzrRD")) >= 0) switch (opt) {
2791 		case 'a':
2792 			xtype = kTypeAscii;
2793 			break;
2794 		case 'A':
2795 			/* Append to remote files, instead of truncating
2796 			 * them first.
2797 			 */
2798 			appendFlag = kAppendYes;
2799 			break;
2800 		case 'f':
2801 		case 'Z':
2802 			/* Do not try to resume a download, even if it
2803 			 * appeared that some of the file was transferred
2804 			 * already.
2805 			 */
2806 			resumeFlag = kResumeNo;
2807 			confirmProc = NoConfirmResumeUploadProc;
2808 			break;
2809 		case 'z':
2810 			/* Special flag that lets you specify the
2811 			 * destination file.  Normally a "put" will
2812 			 * write the file by the same name as the
2813 			 * local file's basename.
2814 			 */
2815 			renameMode = 1;
2816 			break;
2817 		case 'r':
2818 		case 'R':
2819 			recurseFlag = kRecursiveYes;
2820 			/* If the item is a directory, get the
2821 			 * directory and all its contents.
2822 			 */
2823 			recurseFlag = kRecursiveYes;
2824 			break;
2825 		case 'D':
2826 			/* You can delete the local file after
2827 			 * you uploaded it successfully by using
2828 			 * the -DD option.  It requires two -D's
2829 			 * to minimize the odds of accidentally
2830 			 * using a single -D.
2831 			 */
2832 			nD++;
2833 			break;
2834 		default:
2835 			PrintCmdUsage(cmdp);
2836 			return;
2837 	}
2838 
2839 	if (nD >= 2)
2840 		deleteFlag = kDeleteYes;
2841 
2842 	if (renameMode != 0) {
2843 		if (gOptInd > (argc - 2)) {
2844 			PrintCmdUsage(cmdp);
2845 			(void) fprintf(stderr, "\nFor put with rename, try \"put -z local-path-name remote-path-name\".\n");
2846 			return;
2847 		}
2848 		osigint = NcSignal(SIGINT, XferCanceller);
2849 		rc = FTPPutOneFile3(&gConn, argv[gOptInd], argv[gOptInd + 1], xtype, (-1), appendFlag, NULL, NULL, resumeFlag, deleteFlag, confirmProc, 0);
2850 		if (rc < 0)
2851 			FTPPerror(&gConn, rc, kErrCouldNotStartDataTransfer, "put", argv[gOptInd + 1]);
2852 	} else {
2853 		osigint = NcSignal(SIGINT, XferCanceller);
2854 		for (i=gOptInd; i<argc; i++) {
2855 			doGlob = (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes;
2856 			STRNCPY(pattern, argv[i]);
2857 			StrRemoveTrailingSlashes(pattern);
2858 			rc = FTPPutFiles3(&gConn, pattern, dstdir, recurseFlag, doGlob, xtype, appendFlag, NULL, NULL, resumeFlag, deleteFlag, confirmProc, 0);
2859 			if (rc < 0)
2860 				FTPPerror(&gConn, rc, kErrCouldNotStartDataTransfer, "put", argv[i]);
2861 		}
2862 	}
2863 
2864 	/* Really should just flush only the modified directories... */
2865 	FlushLsCache();
2866 
2867 	(void) NcSignal(SIGINT, osigint);
2868 	(void) fflush(stdin);
2869 }	/* PutCmd */
2870 
2871 
2872 
2873 
2874 /* Displays the current remote working directory path. */
2875 void
PwdCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)2876 PwdCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
2877 {
2878 	int result;
2879 	char url[256];
2880 	char olddir[256];
2881 
2882 	ARGSUSED(gUnusedArg);
2883 #ifdef USE_WHAT_SERVER_SAYS_IS_CWD
2884 	result = FTPGetCWD(&gConn, gRemoteCWD, sizeof(gRemoteCWD));
2885 	CurrentURL(url, sizeof(url), 0);
2886 	if (result < 0) {
2887 		FTPPerror(&gConn, result, kErrPWDFailed, "Could not get remote working directory", NULL);
2888 	} else {
2889 		Trace(-1, "%s\n", url);
2890 	}
2891 #else /* USE_CLIENT_SIDE_CALCULATED_CWD */
2892 
2893 	/* Display the current working directory, as
2894 	 * maintained by us.
2895 	 */
2896 	CurrentURL(url, sizeof(url), 0);
2897 	Trace(-1, "  %s\n", url);
2898 	olddir[sizeof(olddir) - 2] = '\0';
2899 	STRNCPY(olddir, gRemoteCWD);
2900 
2901 	/* Now see what the server reports as the CWD.
2902 	 * Because of symlinks, it could be different
2903 	 * from what we are using.
2904 	 */
2905 	result = FTPGetCWD(&gConn, gRemoteCWD, sizeof(gRemoteCWD));
2906 	if ((result == kNoErr) && (strcmp(gRemoteCWD, olddir) != 0)) {
2907 		Trace(-1, "This URL is also valid on this server:\n");
2908 		CurrentURL(url, sizeof(url), 0);
2909 		Trace(-1, "  %s\n", url);
2910 		if (olddir[sizeof(olddir) - 2] == '\0') {
2911 			/* Go back to using the non-resolved version. */
2912 			STRNCPY(gRemoteCWD, olddir);
2913 		}
2914 	}
2915 #endif
2916 }	/* PwdCmd */
2917 
2918 
2919 
2920 
2921 /* Sets a flag so that the command shell exits. */
2922 void
QuitCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)2923 QuitCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
2924 {
2925 	ARGSUSED(gUnusedArg);
2926 	gDoneApplication = 1;
2927 }	/* QuitCmd */
2928 
2929 
2930 
2931 
2932 /* Send a command string to the FTP server, verbatim. */
2933 void
QuoteCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)2934 QuoteCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
2935 {
2936 	char cmdbuf[256];
2937 	int i;
2938 
2939 	ARGSUSED(gUnusedArg);
2940 	(void) STRNCPY(cmdbuf, argv[1]);
2941 	for (i=2; i<argc; i++) {
2942 		(void) STRNCAT(cmdbuf, " ");
2943 		(void) STRNCAT(cmdbuf, argv[i]);
2944 	}
2945 	(void) FTPCmd(&gConn, "%s", cmdbuf);
2946 	PrintResp(&gConn.lastFTPCmdResultLL);
2947 }	/* QuoteCmd */
2948 
2949 
2950 
2951 
2952 /* Expands a remote regex.  Mostly a debugging command. */
2953 void
RGlobCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)2954 RGlobCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
2955 {
2956 	int i;
2957 	int result;
2958 	int np = 0;
2959 	LineList ll;
2960 	LinePtr lp;
2961 
2962 	ARGSUSED(gUnusedArg);
2963 	for (i=1; i<argc; i++) {
2964 		InitLineList(&ll);
2965 		result = FTPRemoteGlob(&gConn, &ll, argv[i], (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
2966 		if (result < 0) {
2967 			FTPPerror(&gConn, result, kErrGlobFailed, "remote glob", argv[i]);
2968 		} else {
2969 			for (lp = ll.first; lp != NULL; lp = lp->next) {
2970 				if (lp->line != NULL) {
2971 					if (np > 0)
2972 						(void) printf(" ");
2973 					(void) printf("%s", lp->line);
2974 					np++;
2975 				}
2976 			}
2977 		}
2978 		DisposeLineListContents(&ll);
2979 	}
2980 	(void) printf("\n");
2981 }	/* RGlobCmd */
2982 
2983 
2984 
2985 
2986 /* Renames a remote file. */
2987 void
RenameCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)2988 RenameCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
2989 {
2990 	int result;
2991 
2992 	ARGSUSED(gUnusedArg);
2993 	result = FTPRename(&gConn, argv[1], argv[2]);
2994 	if (result < 0)
2995 		FTPPerror(&gConn, result, kErrRenameFailed, "rename", argv[1]);
2996 	else {
2997 		/* Really should just flush only the modified directories... */
2998 		FlushLsCache();
2999 	}
3000 }	/* RenameCmd */
3001 
3002 
3003 
3004 
3005 /* Removes a directory on the remote host. */
3006 void
RmdirCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)3007 RmdirCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
3008 {
3009 	int result;
3010 	int i, c;
3011 	int recursive = kRecursiveNo;
3012 
3013 	ARGSUSED(gUnusedArg);
3014 	GetoptReset();
3015 	while ((c = Getopt(argc, argv, "rf")) > 0) switch(c) {
3016 		case 'r':
3017 			recursive = kRecursiveYes;
3018 			break;
3019 		case 'f':
3020 			/* ignore */
3021 			break;
3022 		default:
3023 			PrintCmdUsage(cmdp);
3024 			return;
3025 	}
3026 	for (i=gOptInd; i<argc; i++) {
3027 		result = FTPRmdir(&gConn, argv[i], recursive, (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
3028 		if (result < 0)
3029 			FTPPerror(&gConn, result, kErrRMDFailed, "rmdir", argv[i]);
3030 	}
3031 
3032 	/* Really should just flush only the modified directories... */
3033 	FlushLsCache();
3034 }	/* RmdirCmd */
3035 
3036 
3037 
3038 
3039 /* Asks the remote server for help on how to use its server. */
3040 void
RmtHelpCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)3041 RmtHelpCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
3042 {
3043 	int i, result;
3044 	LineList ll;
3045 	LinePtr lp;
3046 
3047 	ARGSUSED(gUnusedArg);
3048 	if (argc == 1) {
3049 		result = FTPRemoteHelp(&gConn, NULL, &ll);
3050 		if (result < 0)
3051 			FTPPerror(&gConn, result, kErrHELPFailed, "HELP failed", NULL);
3052 		else {
3053 			for (lp = ll.first; lp != NULL; lp = lp->next)
3054 				(void) printf("%s\n", lp->line);
3055 		}
3056 		DisposeLineListContents(&ll);
3057 	} else {
3058 		for (i=1; i<argc; i++) {
3059 			result = FTPRemoteHelp(&gConn, argv[i], &ll);
3060 			if (result < 0)
3061 				FTPPerror(&gConn, result, kErrHELPFailed, "HELP failed for", argv[i]);
3062 			else {
3063 				for (lp = ll.first; lp != NULL; lp = lp->next)
3064 					(void) printf("%s\n", lp->line);
3065 			}
3066 			DisposeLineListContents(&ll);
3067 		}
3068 	}
3069 }	/* RmtHelpCmd */
3070 
3071 
3072 
3073 
3074 /* Show and/or change customizable program settings. These changes are saved
3075  * at the end of the program's run.
3076  */
3077 void
SetCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)3078 SetCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
3079 {
3080 	ARGSUSED(gUnusedArg);
3081 	if (argc < 2)
3082 		Set(NULL, NULL);
3083 	else if (argc == 2)
3084 		Set(argv[1], NULL);
3085 	else
3086 		Set(argv[1], argv[2]);
3087 }	/* SetCmd */
3088 
3089 
3090 
3091 
3092 /* Local shell escape. */
3093 void
ShellCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)3094 ShellCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
3095 {
3096 #if defined(WIN32) || defined(_WINDOWS)
3097 #else
3098 	const char *cp;
3099 	pid_t pid;
3100 	int status;
3101 	vsigproc_t osigint;
3102 
3103 	osigint = NcSignal(SIGINT, (FTPSigProc) SIG_IGN);
3104 	ARGSUSED(gUnusedArg);
3105 	pid = fork();
3106 	if (pid < (pid_t) 0) {
3107 		perror("fork");
3108 	} else if (pid == 0) {
3109 		cp = strrchr(gShell, '/');
3110 		if (cp == NULL)
3111 			cp = gShell;	/* bug */
3112 		else
3113 			cp++;
3114 		if (argc == 1) {
3115 			execl(gShell, cp, NULL);
3116 			perror(gShell);
3117 			exit(1);
3118 		} else {
3119 			execvp(argv[1], (char **) argv + 1);
3120 			perror(gShell);
3121 			exit(1);
3122 		}
3123 	} else {
3124 		/* parent */
3125 		for (;;) {
3126 #ifdef HAVE_WAITPID
3127 			if ((waitpid(pid, &status, 0) < 0) && (errno != EINTR))
3128 				break;
3129 #else
3130 			if ((wait(&status) < 0) && (errno != EINTR))
3131 				break;
3132 #endif
3133 			if (WIFEXITED(status) || WIFSIGNALED(status))
3134 				break;		/* done */
3135 		}
3136 	}
3137 	(void) NcSignal(SIGINT, osigint);
3138 #endif
3139 }	/* ShellCmd */
3140 
3141 
3142 
3143 
3144 /* Send a command string to the FTP server, verbatim. */
3145 void
SiteCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)3146 SiteCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
3147 {
3148 	char cmdbuf[256];
3149 	int i;
3150 
3151 	ARGSUSED(gUnusedArg);
3152 	(void) STRNCPY(cmdbuf, "SITE");
3153 	for (i=1; i<argc; i++) {
3154 		(void) STRNCAT(cmdbuf, " ");
3155 		(void) STRNCAT(cmdbuf, argv[i]);
3156 	}
3157 	(void) FTPCmd(&gConn, "%s", cmdbuf);
3158 	PrintResp(&gConn.lastFTPCmdResultLL);
3159 }	/* SiteCmd */
3160 
3161 
3162 
3163 
3164 static time_t
GetStartSpoolDate(const char * s)3165 GetStartSpoolDate(const char *s)
3166 {
3167 	char *cp;
3168 	char s2[64];
3169 	time_t now, when;
3170 	int toff, n, c, hr, min;
3171 	struct tm lt, *ltp;
3172 
3173 	STRNCPY(s2, s);
3174 	cp = strchr(s2, ':');
3175 	if ((s2[0] == 'n') || (s2[0] == '+')) {
3176 		/* "now + XX hours" or
3177 		 * "+ XX hours"
3178 		 */
3179 		cp = strchr(s2, '+');
3180 		if (cp == NULL)
3181 			return ((time_t) -1);
3182 		++cp;
3183 		toff = 0;
3184 		n = 0;
3185 		(void) sscanf(cp, "%d%n", &toff, &n);
3186 		if ((n <= 0) || (toff <= 0))
3187 			return ((time_t) -1);
3188 		cp += n;
3189 		while ((*cp != '\0') && (!isalpha(*cp)))
3190 			cp++;
3191 		c = *cp;
3192 		if (isupper(c))
3193 			c = tolower(c);
3194 		if (c == 's') {
3195 			/* seconds */
3196 		} else if (c == 'm') {
3197 			/* minutes */
3198 			toff *= 60;
3199 		} else if (c == 'h') {
3200 			/* hours */
3201 			toff *= 3600;
3202 		} else if (c == 'd') {
3203 			/* days */
3204 			toff *= 86400;
3205 		} else {
3206 			/* unrecognized unit */
3207 			return ((time_t) -1);
3208 		}
3209 		time(&now);
3210 		when = now + (time_t) toff;
3211 	} else if (cp != NULL) {
3212 		/* HH:MM, as in "23:38" */
3213 		time(&now);
3214 		ltp = localtime(&now);
3215 		if (ltp == NULL)
3216 			return ((time_t) -1);	/* impossible */
3217 		lt = *ltp;
3218 		*cp = ' ';
3219 		hr = -1;
3220 		min = -1;
3221 		(void) sscanf(s2, "%d%d", &hr, &min);
3222 		if ((hr < 0) || (min < 0))
3223 			return ((time_t) -1);
3224 		lt.tm_hour = hr;
3225 		lt.tm_min = min;
3226 		when = mktime(&lt);
3227 		if ((when == (time_t) -1) || (when == (time_t) 0))
3228 			return (when);
3229 		if (when < now)
3230 			when += (time_t) 86400;
3231 	} else {
3232 		when = UnDate(s2);
3233 	}
3234 	return (when);
3235 }	/* GetStartSpoolDate */
3236 
3237 
3238 
3239 static int
SpoolCheck(void)3240 SpoolCheck(void)
3241 {
3242 	if (CanSpool() < 0) {
3243 #if defined(WIN32) || defined(_WINDOWS)
3244 		(void) printf("Sorry, spooling isn't allowed until you run Setup.exe.\n");
3245 #else
3246 		(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");
3247 #endif
3248 		return (-1);
3249 	} else if (HaveSpool() == 0) {
3250 #if defined(WIN32) || defined(_WINDOWS)
3251 		(void) printf("Sorry, the \"ncftpbatch\" program could not be found.\nPlease re-run Setup to correct this problem.\n");
3252 #else
3253 		(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");
3254 #endif
3255 		return (-1);
3256 	}
3257 	return (0);
3258 }	/* SpoolCheck */
3259 
3260 
3261 
3262 /* Show and/or change customizable program settings. These changes are saved
3263  * at the end of the program's run.
3264  */
3265 void
BGStartCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)3266 BGStartCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
3267 {
3268 	int i, n;
3269 
3270 	ARGSUSED(gUnusedArg);
3271 	if (SpoolCheck() < 0)
3272 		return;
3273 
3274 	if ((argc < 2) || ((n = atoi(argv[1])) < 2)) {
3275 		RunBatch(0, &gConn);
3276 		(void) printf("Background process started.\n");
3277 #if defined(WIN32) || defined(_WINDOWS)
3278 #else
3279 		(void) printf("Watch the \"%s/batchlog\" file to see how it is progressing.\n", gOurDirectoryPath);
3280 #endif
3281 	} else {
3282 		for (i=0; i<n; i++)
3283 			RunBatch(0, &gConn);
3284 		(void) printf("Background processes started.\n");
3285 #if defined(WIN32) || defined(_WINDOWS)
3286 #else
3287 		(void) printf("Watch the \"%s/batchlog\" file to see how it is progressing.\n", gOurDirectoryPath);
3288 #endif
3289 	}
3290 }	/* BGStart */
3291 
3292 
3293 
3294 
3295 /* This commands lets the user change the umask, if the server supports it. */
3296 /* (bgget) */
3297 void
SpoolGetCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)3298 SpoolGetCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
3299 {
3300 	int opt;
3301 	int renameMode = 0;
3302 	int recurseFlag = kRecursiveNo;
3303 	int rc;
3304 	int i;
3305 	int xtype = gBm.xferType;
3306 	int nD = 0;
3307 	int deleteFlag = kDeleteNo;
3308 	time_t when = 0;
3309 	char ldir[256];
3310 	char pattern[256];
3311 	char *lname;
3312 	LineList ll;
3313 	LinePtr lp;
3314 
3315 	ARGSUSED(gUnusedArg);
3316 
3317 	if ((gSavePasswords <= 0) && ((strcmp(gConn.user, "anonymous") != 0) && (strcmp(gConn.user, "ftp") != 0))) {
3318 		(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");
3319 		return;
3320 	} else if (SpoolCheck() < 0) {
3321 		return;
3322 	}
3323 
3324 	GetoptReset();
3325 	while ((opt = Getopt(argc, argv, "@:azfrRD")) >= 0) switch (opt) {
3326 		case '@':
3327 			when = GetStartSpoolDate(gOptArg);
3328 			if ((when == (time_t) -1) || (when == (time_t) 0)) {
3329 				(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");
3330 				return;
3331 			}
3332 			break;
3333 		case 'a':
3334 			xtype = kTypeAscii;
3335 			break;
3336 		case 'z':
3337 			/* Special flag that lets you specify the
3338 			 * destination file.  Normally a "get" will
3339 			 * write the file by the same name as the
3340 			 * remote file's basename.
3341 			 */
3342 			renameMode = 1;
3343 			break;
3344 		case 'r':
3345 		case 'R':
3346 			/* If the item is a directory, get the
3347 			 * directory and all its contents.
3348 			 */
3349 			recurseFlag = kRecursiveYes;
3350 			break;
3351 		case 'D':
3352 			/* You can delete the remote file after
3353 			 * you downloaded it successfully by using
3354 			 * the -DD option.  It requires two -D's
3355 			 * to minimize the odds of accidentally
3356 			 * using a single -D.
3357 			 */
3358 			nD++;
3359 			break;
3360 		default:
3361 			PrintCmdUsage(cmdp);
3362 			return;
3363 	}
3364 
3365 	if (nD >= 2)
3366 		deleteFlag = kDeleteYes;
3367 
3368 	if (FTPGetLocalCWD(ldir, sizeof(ldir)) == NULL) {
3369 		perror("could not get current local directory");
3370 		return;
3371 	}
3372 
3373 	if (renameMode != 0) {
3374 		if (gOptInd > argc - 2) {
3375 			PrintCmdUsage(cmdp);
3376 			return;
3377 		}
3378 		rc = SpoolX(
3379 			"get",
3380 			argv[gOptInd],
3381 			gRemoteCWD,
3382 			argv[gOptInd + 1],
3383 			ldir,
3384 			gConn.host,
3385 			gConn.ip,
3386 			gConn.port,
3387 			gConn.user,
3388 			gConn.pass,
3389 			xtype,
3390 			recurseFlag,
3391 			deleteFlag,
3392 			gConn.dataPortMode,
3393 			NULL,
3394 			NULL,
3395 			NULL,
3396 			when
3397 		);
3398 		if (rc == 0) {
3399 			Trace(-1, "  + Spooled: get %s as %s\n", argv[gOptInd], argv[gOptInd]);
3400 		}
3401 	} else {
3402 		for (i=gOptInd; i<argc; i++) {
3403 			STRNCPY(pattern, argv[i]);
3404 			StrRemoveTrailingSlashes(pattern);
3405 			InitLineList(&ll);
3406 			rc = FTPRemoteGlob(&gConn, &ll, pattern, (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
3407 			if (rc < 0) {
3408 				FTPPerror(&gConn, rc, kErrGlobFailed, argv[0], pattern);
3409 			} else {
3410 				for (lp = ll.first; lp != NULL; lp = lp->next) {
3411 					if (lp->line != NULL) {
3412 						lname = strrchr(lp->line, '/');
3413 						if (lname == NULL)
3414 							lname = lp->line;
3415 						else
3416 							lname++;
3417 						rc = SpoolX(
3418 							"get",
3419 							lp->line,
3420 							gRemoteCWD,
3421 							lname,
3422 							ldir,
3423 							gConn.host,
3424 							gConn.ip,
3425 							gConn.port,
3426 							gConn.user,
3427 							gConn.pass,
3428 							xtype,
3429 							recurseFlag,
3430 							deleteFlag,
3431 							gConn.dataPortMode,
3432 							NULL,
3433 							NULL,
3434 							NULL,
3435 							when
3436 						);
3437 						if (rc == 0) {
3438 							Trace(-1, "  + Spooled: get %s\n", lp->line);
3439 						}
3440 					}
3441 				}
3442 			}
3443 			DisposeLineListContents(&ll);
3444 		}
3445 	}
3446 }	/* SpoolGetCmd */
3447 
3448 
3449 
3450 
3451 /* This commands lets the user change the umask, if the server supports it. */
3452 /* (bgput) */
3453 void
SpoolPutCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)3454 SpoolPutCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
3455 {
3456 	int opt;
3457 	int renameMode = 0;
3458 	int recurseFlag = kRecursiveNo;
3459 	int rc;
3460 	int i;
3461 	int xtype = gBm.xferType;
3462 	int nD = 0;
3463 	int deleteFlag = kDeleteNo;
3464 	time_t when = 0;
3465 	char ldir[256];
3466 	char pattern[256];
3467 	LineList ll;
3468 	LinePtr lp;
3469 	char *rname;
3470 
3471 	ARGSUSED(gUnusedArg);
3472 
3473 	if ((gSavePasswords <= 0) && ((strcmp(gConn.user, "anonymous") != 0) && (strcmp(gConn.user, "ftp") != 0))) {
3474 		(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");
3475 		return;
3476 	} else if (SpoolCheck() < 0) {
3477 		return;
3478 	}
3479 
3480 	GetoptReset();
3481 	while ((opt = Getopt(argc, argv, "@:azrRD")) >= 0) switch (opt) {
3482 		case '@':
3483 			when = GetStartSpoolDate(gOptArg);
3484 			if ((when == (time_t) -1) || (when == (time_t) 0)) {
3485 				(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");
3486 				return;
3487 			}
3488 			break;
3489 		case 'a':
3490 			xtype = kTypeAscii;
3491 			break;
3492 		case 'z':
3493 			/* Special flag that lets you specify the
3494 			 * destination file.  Normally a "get" will
3495 			 * write the file by the same name as the
3496 			 * remote file's basename.
3497 			 */
3498 			renameMode = 1;
3499 			break;
3500 		case 'r':
3501 		case 'R':
3502 			/* If the item is a directory, get the
3503 			 * directory and all its contents.
3504 			 */
3505 			recurseFlag = kRecursiveYes;
3506 			break;
3507 		case 'D':
3508 			/* You can delete the remote file after
3509 			 * you downloaded it successfully by using
3510 			 * the -DD option.  It requires two -D's
3511 			 * to minimize the odds of accidentally
3512 			 * using a single -D.
3513 			 */
3514 			nD++;
3515 			break;
3516 		default:
3517 			PrintCmdUsage(cmdp);
3518 			return;
3519 	}
3520 
3521 	if (nD >= 2)
3522 		deleteFlag = kDeleteYes;
3523 
3524 	if (FTPGetLocalCWD(ldir, sizeof(ldir)) == NULL) {
3525 		perror("could not get current local directory");
3526 		return;
3527 	}
3528 
3529 	if (renameMode != 0) {
3530 		if (gOptInd > argc - 2) {
3531 			PrintCmdUsage(cmdp);
3532 			return;
3533 		}
3534 		rc = SpoolX(
3535 			"put",
3536 			argv[gOptInd + 1],
3537 			gRemoteCWD,
3538 			argv[gOptInd + 0],
3539 			ldir,
3540 			gConn.host,
3541 			gConn.ip,
3542 			gConn.port,
3543 			gConn.user,
3544 			gConn.pass,
3545 			xtype,
3546 			recurseFlag,
3547 			deleteFlag,
3548 			gConn.dataPortMode,
3549 			NULL,
3550 			NULL,
3551 			NULL,
3552 			when
3553 		);
3554 		if (rc == 0) {
3555 			Trace(-1, "  + Spooled: put %s as %s\n", argv[gOptInd], argv[gOptInd]);
3556 		}
3557 	} else {
3558 		for (i=gOptInd; i<argc; i++) {
3559 			STRNCPY(pattern, argv[i]);
3560 			StrRemoveTrailingSlashes(pattern);
3561 			InitLineList(&ll);
3562 			rc = FTPLocalGlob(&gConn, &ll, pattern, (aip->noglobargv[i] != 0) ? kGlobNo: kGlobYes);
3563 			if (rc < 0) {
3564 				FTPPerror(&gConn, rc, kErrGlobFailed, "local glob", pattern);
3565 			} else {
3566 				for (lp = ll.first; lp != NULL; lp = lp->next) {
3567 					if (lp->line != NULL) {
3568 						rname = strrchr(lp->line, '/');
3569 						if (rname == NULL)
3570 							rname = lp->line;
3571 						else
3572 							rname++;
3573 						rc = SpoolX(
3574 							"put",
3575 							rname,
3576 							gRemoteCWD,
3577 							lp->line,
3578 							ldir,
3579 							gConn.host,
3580 							gConn.ip,
3581 							gConn.port,
3582 							gConn.user,
3583 							gConn.pass,
3584 							xtype,
3585 							recurseFlag,
3586 							deleteFlag,
3587 							gConn.dataPortMode,
3588 							NULL,
3589 							NULL,
3590 							NULL,
3591 							when
3592 						);
3593 						if (rc == 0) {
3594 							Trace(-1, "  + Spooled: put %s\n", lp->line);
3595 						}
3596 					}
3597 				}
3598 			}
3599 			DisposeLineListContents(&ll);
3600 		}
3601 	}
3602 }	/* SpoolGetCmd */
3603 
3604 
3605 
3606 
3607 /* This commands lets the user change the umask, if the server supports it. */
3608 void
SymlinkCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)3609 SymlinkCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
3610 {
3611 	int result;
3612 
3613 	ARGSUSED(gUnusedArg);
3614 	result = FTPSymlink(&gConn, argv[1], argv[2]);
3615 	if (result < 0)
3616 		FTPPerror(&gConn, result, kErrSYMLINKFailed, "symlink", argv[1]);
3617 
3618 	/* Really should just flush only the modified directories... */
3619 	FlushLsCache();
3620 }	/* SymlinkCmd */
3621 
3622 
3623 
3624 
3625 /* This commands lets the user change the transfer type to use. */
3626 void
TypeCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)3627 TypeCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
3628 {
3629 	int c;
3630 	int result;
3631 	const char *cs;
3632 
3633 	ARGSUSED(gUnusedArg);
3634 	if (argc < 2) {
3635 		c = argv[0][0];
3636 		if (c == 't') {
3637 			if (gBm.xferType == kTypeAscii) {
3638 				c = kTypeAscii;
3639 				cs = "ASCII";
3640 			} else if (gBm.xferType == kTypeEbcdic) {
3641 				c = kTypeEbcdic;
3642 				cs = "EBCDIC";
3643 			} else {
3644 				c = kTypeBinary;
3645 				cs = "binary/image";
3646 			}
3647 			Trace(-1, "Type is %c (%s).\n", c, cs);
3648 		} else {
3649 			result = FTPSetTransferType(&gConn, c);
3650 			if (result < 0) {
3651 				FTPPerror(&gConn, result, kErrTYPEFailed, "Type", argv[1]);
3652 			} else {
3653 				gBm.xferType = gConn.curTransferType;
3654 			}
3655 		}
3656 	} else {
3657 		c = argv[1][0];
3658 		result = FTPSetTransferType(&gConn, c);
3659 		if (result < 0) {
3660 			FTPPerror(&gConn, result, kErrTYPEFailed, "Type", argv[1]);
3661 		} else {
3662 			gBm.xferType = gConn.curTransferType;
3663 		}
3664 	}
3665 }	/* TypeCmd */
3666 
3667 
3668 
3669 
3670 /* This commands lets the user change the umask, if the server supports it. */
3671 void
UmaskCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)3672 UmaskCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
3673 {
3674 	int result;
3675 
3676 	ARGSUSED(gUnusedArg);
3677 	result = FTPUmask(&gConn, argv[1]);
3678 	if (result < 0)
3679 		FTPPerror(&gConn, result, kErrUmaskFailed, "umask", argv[1]);
3680 }	/* UmaskCmd */
3681 
3682 
3683 
3684 
3685 /* Show the version information. */
3686 void
VersionCmd(const int argc,const char ** const argv,const CommandPtr cmdp,const ArgvInfoPtr aip)3687 VersionCmd(const int argc, const char **const argv, const CommandPtr cmdp, const ArgvInfoPtr aip)
3688 {
3689 	ARGSUSED(gUnusedArg);
3690 	(void) printf("Version:          %s\n", gVersion + 5);
3691 	(void) printf("Author:           Mike Gleason (ncftp@ncftp.com)\n");
3692 #ifndef BETA
3693 	(void) printf("Archived at:      ftp://ftp.ncftp.com/ncftp/\n");
3694 #endif
3695 	(void) printf("Library Version:  %s\n", gLibNcFTPVersion + 5);
3696 #ifdef __DATE__
3697 	(void) printf("Compile Date:     %s\n", __DATE__);
3698 #endif
3699 	if (gOS[0] != '\0')
3700 		(void) printf("Platform:         %s\n", gOS);
3701 }	/* VersionCmd */
3702