xref: /netbsd-src/usr.bin/ftp/util.c (revision 8e6ab8837d8d6b9198e67c1c445300b483e2f304)
1 /*	$NetBSD: util.c,v 1.113 2003/07/31 06:57:07 lukem Exp $	*/
2 
3 /*-
4  * Copyright (c) 1997-2003 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Luke Mewburn.
9  *
10  * This code is derived from software contributed to The NetBSD Foundation
11  * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
12  * NASA Ames Research Center.
13  *
14  * Redistribution and use in source and binary forms, with or without
15  * modification, are permitted provided that the following conditions
16  * are met:
17  * 1. Redistributions of source code must retain the above copyright
18  *    notice, this list of conditions and the following disclaimer.
19  * 2. Redistributions in binary form must reproduce the above copyright
20  *    notice, this list of conditions and the following disclaimer in the
21  *    documentation and/or other materials provided with the distribution.
22  * 3. All advertising materials mentioning features or use of this software
23  *    must display the following acknowledgement:
24  *	This product includes software developed by the NetBSD
25  *	Foundation, Inc. and its contributors.
26  * 4. Neither the name of The NetBSD Foundation nor the names of its
27  *    contributors may be used to endorse or promote products derived
28  *    from this software without specific prior written permission.
29  *
30  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
31  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
32  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
33  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
34  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
35  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
36  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
37  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
38  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
39  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
40  * POSSIBILITY OF SUCH DAMAGE.
41  */
42 
43 /*
44  * Copyright (c) 1985, 1989, 1993, 1994
45  *	The Regents of the University of California.  All rights reserved.
46  *
47  * Redistribution and use in source and binary forms, with or without
48  * modification, are permitted provided that the following conditions
49  * are met:
50  * 1. Redistributions of source code must retain the above copyright
51  *    notice, this list of conditions and the following disclaimer.
52  * 2. Redistributions in binary form must reproduce the above copyright
53  *    notice, this list of conditions and the following disclaimer in the
54  *    documentation and/or other materials provided with the distribution.
55  * 3. All advertising materials mentioning features or use of this software
56  *    must display the following acknowledgement:
57  *	This product includes software developed by the University of
58  *	California, Berkeley and its contributors.
59  * 4. Neither the name of the University nor the names of its contributors
60  *    may be used to endorse or promote products derived from this software
61  *    without specific prior written permission.
62  *
63  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
64  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
65  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
66  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
67  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
68  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
69  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
70  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
71  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
72  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
73  * SUCH DAMAGE.
74  */
75 
76 #include <sys/cdefs.h>
77 #ifndef lint
78 __RCSID("$NetBSD: util.c,v 1.113 2003/07/31 06:57:07 lukem Exp $");
79 #endif /* not lint */
80 
81 /*
82  * FTP User Program -- Misc support routines
83  */
84 #include <sys/types.h>
85 #include <sys/socket.h>
86 #include <sys/ioctl.h>
87 #include <sys/time.h>
88 #include <netinet/in.h>
89 #include <arpa/ftp.h>
90 
91 #include <ctype.h>
92 #include <err.h>
93 #include <errno.h>
94 #include <fcntl.h>
95 #include <glob.h>
96 #include <signal.h>
97 #include <limits.h>
98 #include <netdb.h>
99 #include <stdio.h>
100 #include <stdlib.h>
101 #include <string.h>
102 #include <termios.h>
103 #include <time.h>
104 #include <tzfile.h>
105 #include <unistd.h>
106 
107 #include "ftp_var.h"
108 
109 /*
110  * Connect to peer server and auto-login, if possible.
111  */
112 void
113 setpeer(int argc, char *argv[])
114 {
115 	char *host;
116 	char *port;
117 
118 	if (argc == 0)
119 		goto usage;
120 	if (connected) {
121 		fprintf(ttyout, "Already connected to %s, use close first.\n",
122 		    hostname);
123 		code = -1;
124 		return;
125 	}
126 	if (argc < 2)
127 		(void)another(&argc, &argv, "to");
128 	if (argc < 2 || argc > 3) {
129  usage:
130 		fprintf(ttyout, "usage: %s host-name [port]\n", argv[0]);
131 		code = -1;
132 		return;
133 	}
134 	if (gatemode)
135 		port = gateport;
136 	else
137 		port = ftpport;
138 	if (argc > 2)
139 		port = argv[2];
140 
141 	if (gatemode) {
142 		if (gateserver == NULL || *gateserver == '\0')
143 			errx(1, "gateserver not defined (shouldn't happen)");
144 		host = hookup(gateserver, port);
145 	} else
146 		host = hookup(argv[1], port);
147 
148 	if (host) {
149 		if (gatemode && verbose) {
150 			fprintf(ttyout,
151 			    "Connecting via pass-through server %s\n",
152 			    gateserver);
153 		}
154 
155 		connected = 1;
156 		/*
157 		 * Set up defaults for FTP.
158 		 */
159 		(void)strlcpy(typename, "ascii", sizeof(typename));
160 		type = TYPE_A;
161 		curtype = TYPE_A;
162 		(void)strlcpy(formname, "non-print", sizeof(formname));
163 		form = FORM_N;
164 		(void)strlcpy(modename, "stream", sizeof(modename));
165 		mode = MODE_S;
166 		(void)strlcpy(structname, "file", sizeof(structname));
167 		stru = STRU_F;
168 		(void)strlcpy(bytename, "8", sizeof(bytename));
169 		bytesize = 8;
170 		if (autologin)
171 			(void)ftp_login(argv[1], NULL, NULL);
172 	}
173 }
174 
175 static void
176 parse_feat(const char *line)
177 {
178 
179 			/*
180 			 * work-around broken ProFTPd servers that can't
181 			 * even obey RFC 2389.
182 			 */
183 	while (*line && isspace((int)*line))
184 		line++;
185 
186 	if (strcasecmp(line, "MDTM") == 0)
187 		features[FEAT_MDTM] = 1;
188 	else if (strncasecmp(line, "MLST", sizeof("MLST") - 1) == 0) {
189 		features[FEAT_MLST] = 1;
190 	} else if (strcasecmp(line, "REST STREAM") == 0)
191 		features[FEAT_REST_STREAM] = 1;
192 	else if (strcasecmp(line, "SIZE") == 0)
193 		features[FEAT_SIZE] = 1;
194 	else if (strcasecmp(line, "TVFS") == 0)
195 		features[FEAT_TVFS] = 1;
196 }
197 
198 /*
199  * Determine the remote system type (SYST) and features (FEAT).
200  * Call after a successful login (i.e, connected = -1)
201  */
202 void
203 getremoteinfo(void)
204 {
205 	int overbose, i;
206 
207 	overbose = verbose;
208 	if (debug == 0)
209 		verbose = -1;
210 
211 			/* determine remote system type */
212 	if (command("SYST") == COMPLETE) {
213 		if (overbose) {
214 			char *cp, c;
215 
216 			c = 0;
217 			cp = strchr(reply_string + 4, ' ');
218 			if (cp == NULL)
219 				cp = strchr(reply_string + 4, '\r');
220 			if (cp) {
221 				if (cp[-1] == '.')
222 					cp--;
223 				c = *cp;
224 				*cp = '\0';
225 			}
226 
227 			fprintf(ttyout, "Remote system type is %s.\n",
228 			    reply_string + 4);
229 			if (cp)
230 				*cp = c;
231 		}
232 		if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) {
233 			if (proxy)
234 				unix_proxy = 1;
235 			else
236 				unix_server = 1;
237 			/*
238 			 * Set type to 0 (not specified by user),
239 			 * meaning binary by default, but don't bother
240 			 * telling server.  We can use binary
241 			 * for text files unless changed by the user.
242 			 */
243 			type = 0;
244 			(void)strlcpy(typename, "binary", sizeof(typename));
245 			if (overbose)
246 			    fprintf(ttyout,
247 				"Using %s mode to transfer files.\n",
248 				typename);
249 		} else {
250 			if (proxy)
251 				unix_proxy = 0;
252 			else
253 				unix_server = 0;
254 			if (overbose &&
255 			    !strncmp(reply_string, "215 TOPS20", 10))
256 				fputs(
257 "Remember to set tenex mode when transferring binary files from this machine.\n",
258 				    ttyout);
259 		}
260 	}
261 
262 			/* determine features (if any) */
263 	for (i = 0; i < FEAT_max; i++)
264 		features[i] = -1;
265 	reply_callback = parse_feat;
266 	if (command("FEAT") == COMPLETE) {
267 		for (i = 0; i < FEAT_max; i++) {
268 			if (features[i] == -1)
269 				features[i] = 0;
270 		}
271 		features[FEAT_FEAT] = 1;
272 	} else
273 		features[FEAT_FEAT] = 0;
274 	if (debug) {
275 #define DEBUG_FEAT(x) fprintf(ttyout, "features[" #x "] = %d\n", features[(x)])
276 		DEBUG_FEAT(FEAT_FEAT);
277 		DEBUG_FEAT(FEAT_MDTM);
278 		DEBUG_FEAT(FEAT_MLST);
279 		DEBUG_FEAT(FEAT_REST_STREAM);
280 		DEBUG_FEAT(FEAT_SIZE);
281 		DEBUG_FEAT(FEAT_TVFS);
282 #undef DEBUG_FEAT
283 	}
284 	reply_callback = NULL;
285 
286 	verbose = overbose;
287 }
288 
289 /*
290  * Reset the various variables that indicate connection state back to
291  * disconnected settings.
292  * The caller is responsible for issuing any commands to the remote server
293  * to perform a clean shutdown before this is invoked.
294  */
295 void
296 cleanuppeer(void)
297 {
298 
299 	if (cout)
300 		(void)fclose(cout);
301 	cout = NULL;
302 	connected = 0;
303 	unix_server = 0;
304 	unix_proxy = 0;
305 			/*
306 			 * determine if anonftp was specifically set with -a
307 			 * (1), or implicitly set by auto_fetch() (2). in the
308 			 * latter case, disable after the current xfer
309 			 */
310 	if (anonftp == 2)
311 		anonftp = 0;
312 	data = -1;
313 	epsv4bad = 0;
314 	if (username)
315 		free(username);
316 	username = NULL;
317 	if (!proxy)
318 		macnum = 0;
319 }
320 
321 /*
322  * Top-level signal handler for interrupted commands.
323  */
324 void
325 intr(int dummy)
326 {
327 
328 	alarmtimer(0);
329 	if (fromatty)
330 		write(fileno(ttyout), "\n", 1);
331 	siglongjmp(toplevel, 1);
332 }
333 
334 /*
335  * Signal handler for lost connections; cleanup various elements of
336  * the connection state, and call cleanuppeer() to finish it off.
337  */
338 void
339 lostpeer(int dummy)
340 {
341 	int oerrno = errno;
342 
343 	alarmtimer(0);
344 	if (connected) {
345 		if (cout != NULL) {
346 			(void)shutdown(fileno(cout), 1+1);
347 			(void)fclose(cout);
348 			cout = NULL;
349 		}
350 		if (data >= 0) {
351 			(void)shutdown(data, 1+1);
352 			(void)close(data);
353 			data = -1;
354 		}
355 		connected = 0;
356 	}
357 	pswitch(1);
358 	if (connected) {
359 		if (cout != NULL) {
360 			(void)shutdown(fileno(cout), 1+1);
361 			(void)fclose(cout);
362 			cout = NULL;
363 		}
364 		connected = 0;
365 	}
366 	proxflag = 0;
367 	pswitch(0);
368 	cleanuppeer();
369 	errno = oerrno;
370 }
371 
372 
373 /*
374  * Login to remote host, using given username & password if supplied.
375  * Return non-zero if successful.
376  */
377 int
378 ftp_login(const char *host, const char *user, const char *pass)
379 {
380 	char tmp[80];
381 	const char *acct;
382 	int n, aflag, rval, freeuser, freepass, freeacct;
383 
384 	acct = NULL;
385 	aflag = rval = freeuser = freepass = freeacct = 0;
386 
387 	if (debug)
388 		fprintf(ttyout, "ftp_login: user `%s' pass `%s' host `%s'\n",
389 		    user ? user : "<null>", pass ? pass : "<null>",
390 		    host ? host : "<null>");
391 
392 
393 	/*
394 	 * Set up arguments for an anonymous FTP session, if necessary.
395 	 */
396 	if (anonftp) {
397 		user = "anonymous";	/* as per RFC 1635 */
398 		pass = getoptionvalue("anonpass");
399 	}
400 
401 	if (user == NULL)
402 		freeuser = 1;
403 	if (pass == NULL)
404 		freepass = 1;
405 	freeacct = 1;
406 	if (ruserpass(host, &user, &pass, &acct) < 0) {
407 		code = -1;
408 		goto cleanup_ftp_login;
409 	}
410 
411 	while (user == NULL) {
412 		if (localname)
413 			fprintf(ttyout, "Name (%s:%s): ", host, localname);
414 		else
415 			fprintf(ttyout, "Name (%s): ", host);
416 		*tmp = '\0';
417 		if (fgets(tmp, sizeof(tmp) - 1, stdin) == NULL) {
418 			fprintf(ttyout, "\nEOF received; login aborted.\n");
419 			clearerr(stdin);
420 			code = -1;
421 			goto cleanup_ftp_login;
422 		}
423 		tmp[strlen(tmp) - 1] = '\0';
424 		freeuser = 0;
425 		if (*tmp == '\0')
426 			user = localname;
427 		else
428 			user = tmp;
429 	}
430 
431 	if (gatemode) {
432 		char *nuser;
433 		int len;
434 
435 		len = strlen(user) + 1 + strlen(host) + 1;
436 		nuser = xmalloc(len);
437 		(void)strlcpy(nuser, user, len);
438 		(void)strlcat(nuser, "@",  len);
439 		(void)strlcat(nuser, host, len);
440 		freeuser = 1;
441 		user = nuser;
442 	}
443 
444 	n = command("USER %s", user);
445 	if (n == CONTINUE) {
446 		if (pass == NULL) {
447 			freepass = 0;
448 			pass = getpass("Password:");
449 		}
450 		n = command("PASS %s", pass);
451 	}
452 	if (n == CONTINUE) {
453 		aflag++;
454 		if (acct == NULL) {
455 			freeacct = 0;
456 			acct = getpass("Account:");
457 		}
458 		if (acct[0] == '\0') {
459 			warnx("Login failed.");
460 			goto cleanup_ftp_login;
461 		}
462 		n = command("ACCT %s", acct);
463 	}
464 	if ((n != COMPLETE) ||
465 	    (!aflag && acct != NULL && command("ACCT %s", acct) != COMPLETE)) {
466 		warnx("Login failed.");
467 		goto cleanup_ftp_login;
468 	}
469 	rval = 1;
470 	username = xstrdup(user);
471 	if (proxy)
472 		goto cleanup_ftp_login;
473 
474 	connected = -1;
475 	getremoteinfo();
476 	for (n = 0; n < macnum; ++n) {
477 		if (!strcmp("init", macros[n].mac_name)) {
478 			(void)strlcpy(line, "$init", sizeof(line));
479 			makeargv();
480 			domacro(margc, margv);
481 			break;
482 		}
483 	}
484 	updateremotepwd();
485 
486  cleanup_ftp_login:
487 	if (user != NULL && freeuser)
488 		free((char *)user);
489 	if (pass != NULL && freepass)
490 		free((char *)pass);
491 	if (acct != NULL && freeacct)
492 		free((char *)acct);
493 	return (rval);
494 }
495 
496 /*
497  * `another' gets another argument, and stores the new argc and argv.
498  * It reverts to the top level (via intr()) on EOF/error.
499  *
500  * Returns false if no new arguments have been added.
501  */
502 int
503 another(int *pargc, char ***pargv, const char *prompt)
504 {
505 	int len = strlen(line), ret;
506 
507 	if (len >= sizeof(line) - 3) {
508 		fputs("sorry, arguments too long.\n", ttyout);
509 		intr(0);
510 	}
511 	fprintf(ttyout, "(%s) ", prompt);
512 	line[len++] = ' ';
513 	if (fgets(&line[len], sizeof(line) - len, stdin) == NULL) {
514 		clearerr(stdin);
515 		intr(0);
516 	}
517 	len += strlen(&line[len]);
518 	if (len > 0 && line[len - 1] == '\n')
519 		line[len - 1] = '\0';
520 	makeargv();
521 	ret = margc > *pargc;
522 	*pargc = margc;
523 	*pargv = margv;
524 	return (ret);
525 }
526 
527 /*
528  * glob files given in argv[] from the remote server.
529  * if errbuf isn't NULL, store error messages there instead
530  * of writing to the screen.
531  */
532 char *
533 remglob(char *argv[], int doswitch, char **errbuf)
534 {
535         char temp[MAXPATHLEN];
536         static char buf[MAXPATHLEN];
537         static FILE *ftemp = NULL;
538         static char **args;
539         int oldverbose, oldhash, oldprogress, fd, len;
540         char *cp, *mode;
541 
542         if (!mflag || !connected) {
543                 if (!doglob)
544                         args = NULL;
545                 else {
546                         if (ftemp) {
547                                 (void)fclose(ftemp);
548                                 ftemp = NULL;
549                         }
550                 }
551                 return (NULL);
552         }
553         if (!doglob) {
554                 if (args == NULL)
555                         args = argv;
556                 if ((cp = *++args) == NULL)
557                         args = NULL;
558                 return (cp);
559         }
560         if (ftemp == NULL) {
561 		len = strlcpy(temp, tmpdir, sizeof(temp));
562 		if (temp[len - 1] != '/')
563 			(void)strlcat(temp, "/", sizeof(temp));
564 		(void)strlcat(temp, TMPFILE, sizeof(temp));
565                 if ((fd = mkstemp(temp)) < 0) {
566                         warn("unable to create temporary file %s", temp);
567                         return (NULL);
568                 }
569                 close(fd);
570                 oldverbose = verbose;
571 		verbose = (errbuf != NULL) ? -1 : 0;
572                 oldhash = hash;
573 		oldprogress = progress;
574                 hash = 0;
575 		progress = 0;
576                 if (doswitch)
577                         pswitch(!proxy);
578                 for (mode = "w"; *++argv != NULL; mode = "a")
579                         recvrequest("NLST", temp, *argv, mode, 0, 0);
580 		if ((code / 100) != COMPLETE) {
581 			if (errbuf != NULL)
582 				*errbuf = reply_string;
583 		}
584                 if (doswitch)
585                         pswitch(!proxy);
586                 verbose = oldverbose;
587 		hash = oldhash;
588 		progress = oldprogress;
589                 ftemp = fopen(temp, "r");
590                 (void)unlink(temp);
591                 if (ftemp == NULL) {
592 			if (errbuf == NULL)
593 				fputs(
594 				    "can't find list of remote files, oops.\n",
595 				    ttyout);
596 			else
597 				*errbuf =
598 				    "can't find list of remote files, oops.";
599                         return (NULL);
600                 }
601         }
602         if (fgets(buf, sizeof(buf), ftemp) == NULL) {
603                 (void)fclose(ftemp);
604 		ftemp = NULL;
605                 return (NULL);
606         }
607         if ((cp = strchr(buf, '\n')) != NULL)
608                 *cp = '\0';
609         return (buf);
610 }
611 
612 /*
613  * Glob a local file name specification with the expectation of a single
614  * return value. Can't control multiple values being expanded from the
615  * expression, we return only the first.
616  * Returns NULL on error, or a pointer to a buffer containing the filename
617  * that's the caller's responsiblity to free(3) when finished with.
618  */
619 char *
620 globulize(const char *pattern)
621 {
622 	glob_t gl;
623 	int flags;
624 	char *p;
625 
626 	if (!doglob)
627 		return (xstrdup(pattern));
628 
629 	flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
630 	memset(&gl, 0, sizeof(gl));
631 	if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) {
632 		warnx("%s: not found", pattern);
633 		globfree(&gl);
634 		return (NULL);
635 	}
636 	p = xstrdup(gl.gl_pathv[0]);
637 	globfree(&gl);
638 	return (p);
639 }
640 
641 /*
642  * determine size of remote file
643  */
644 off_t
645 remotesize(const char *file, int noisy)
646 {
647 	int overbose, r;
648 	off_t size;
649 
650 	overbose = verbose;
651 	size = -1;
652 	if (debug == 0)
653 		verbose = -1;
654 	if (! features[FEAT_SIZE]) {
655 		if (noisy)
656 			fprintf(ttyout,
657 			    "SIZE is not supported by remote server.\n");
658 		goto cleanup_remotesize;
659 	}
660 	r = command("SIZE %s", file);
661 	if (r == COMPLETE) {
662 		char *cp, *ep;
663 
664 		cp = strchr(reply_string, ' ');
665 		if (cp != NULL) {
666 			cp++;
667 			size = STRTOLL(cp, &ep, 10);
668 			if (*ep != '\0' && !isspace((unsigned char)*ep))
669 				size = -1;
670 		}
671 	} else {
672 		if (r == ERROR && code == 500 && features[FEAT_SIZE] == -1)
673 			features[FEAT_SIZE] = 0;
674 		if (noisy && debug == 0) {
675 			fputs(reply_string, ttyout);
676 			putc('\n', ttyout);
677 		}
678 	}
679  cleanup_remotesize:
680 	verbose = overbose;
681 	return (size);
682 }
683 
684 /*
685  * determine last modification time (in GMT) of remote file
686  */
687 time_t
688 remotemodtime(const char *file, int noisy)
689 {
690 	int	overbose, ocode, r;
691 	time_t	rtime;
692 
693 	overbose = verbose;
694 	ocode = code;
695 	rtime = -1;
696 	if (debug == 0)
697 		verbose = -1;
698 	if (! features[FEAT_MDTM]) {
699 		if (noisy)
700 			fprintf(ttyout,
701 			    "MDTM is not supported by remote server.\n");
702 		goto cleanup_parse_time;
703 	}
704 	r = command("MDTM %s", file);
705 	if (r == COMPLETE) {
706 		struct tm timebuf;
707 		char *timestr, *frac;
708 		int yy, mo, day, hour, min, sec;
709 
710 		/*
711 		 * time-val = 14DIGIT [ "." 1*DIGIT ]
712 		 *		YYYYMMDDHHMMSS[.sss]
713 		 * mdtm-response = "213" SP time-val CRLF / error-response
714 		 */
715 		timestr = reply_string + 4;
716 
717 					/*
718 					 * parse fraction.
719 					 * XXX: ignored for now
720 					 */
721 		frac = strchr(timestr, '\r');
722 		if (frac != NULL)
723 			*frac = '\0';
724 		frac = strchr(timestr, '.');
725 		if (frac != NULL)
726 			*frac++ = '\0';
727 		if (strlen(timestr) == 15 && strncmp(timestr, "191", 3) == 0) {
728 			/*
729 			 * XXX:	Workaround for lame ftpd's that return
730 			 *	`19100' instead of `2000'
731 			 */
732 			fprintf(ttyout,
733 	    "Y2K warning! Incorrect time-val `%s' received from server.\n",
734 			    timestr);
735 			timestr++;
736 			timestr[0] = '2';
737 			timestr[1] = '0';
738 			fprintf(ttyout, "Converted to `%s'\n", timestr);
739 		}
740 		if (strlen(timestr) != 14 ||
741 		    sscanf(timestr, "%04d%02d%02d%02d%02d%02d",
742 			&yy, &mo, &day, &hour, &min, &sec) != 6) {
743  bad_parse_time:
744 			fprintf(ttyout, "Can't parse time `%s'.\n", timestr);
745 			goto cleanup_parse_time;
746 		}
747 		memset(&timebuf, 0, sizeof(timebuf));
748 		timebuf.tm_sec = sec;
749 		timebuf.tm_min = min;
750 		timebuf.tm_hour = hour;
751 		timebuf.tm_mday = day;
752 		timebuf.tm_mon = mo - 1;
753 		timebuf.tm_year = yy - TM_YEAR_BASE;
754 		timebuf.tm_isdst = -1;
755 		rtime = timegm(&timebuf);
756 		if (rtime == -1) {
757 			if (noisy || debug != 0)
758 				goto bad_parse_time;
759 			else
760 				goto cleanup_parse_time;
761 		} else if (debug)
762 			fprintf(ttyout, "parsed date as: %s", ctime(&rtime));
763 	} else {
764 		if (r == ERROR && code == 500 && features[FEAT_MDTM] == -1)
765 			features[FEAT_MDTM] = 0;
766 		if (noisy && debug == 0) {
767 			fputs(reply_string, ttyout);
768 			putc('\n', ttyout);
769 		}
770 	}
771  cleanup_parse_time:
772 	verbose = overbose;
773 	if (rtime == -1)
774 		code = ocode;
775 	return (rtime);
776 }
777 
778 /*
779  * update global `remotepwd', which contains the state of the remote cwd
780  */
781 void
782 updateremotepwd(void)
783 {
784 	int	 overbose, ocode, i;
785 	char	*cp;
786 
787 	overbose = verbose;
788 	ocode = code;
789 	if (debug == 0)
790 		verbose = -1;
791 	if (command("PWD") != COMPLETE)
792 		goto badremotepwd;
793 	cp = strchr(reply_string, ' ');
794 	if (cp == NULL || cp[0] == '\0' || cp[1] != '"')
795 		goto badremotepwd;
796 	cp += 2;
797 	for (i = 0; *cp && i < sizeof(remotepwd) - 1; i++, cp++) {
798 		if (cp[0] == '"') {
799 			if (cp[1] == '"')
800 				cp++;
801 			else
802 				break;
803 		}
804 		remotepwd[i] = *cp;
805 	}
806 	remotepwd[i] = '\0';
807 	if (debug)
808 		fprintf(ttyout, "got remotepwd as `%s'\n", remotepwd);
809 	goto cleanupremotepwd;
810  badremotepwd:
811 	remotepwd[0]='\0';
812  cleanupremotepwd:
813 	verbose = overbose;
814 	code = ocode;
815 }
816 
817 
818 /*
819  * List words in stringlist, vertically arranged
820  */
821 void
822 list_vertical(StringList *sl)
823 {
824 	int i, j, w;
825 	int columns, width, lines;
826 	char *p;
827 
828 	width = 0;
829 
830 	for (i = 0 ; i < sl->sl_cur ; i++) {
831 		w = strlen(sl->sl_str[i]);
832 		if (w > width)
833 			width = w;
834 	}
835 	width = (width + 8) &~ 7;
836 
837 	columns = ttywidth / width;
838 	if (columns == 0)
839 		columns = 1;
840 	lines = (sl->sl_cur + columns - 1) / columns;
841 	for (i = 0; i < lines; i++) {
842 		for (j = 0; j < columns; j++) {
843 			p = sl->sl_str[j * lines + i];
844 			if (p)
845 				fputs(p, ttyout);
846 			if (j * lines + i + lines >= sl->sl_cur) {
847 				putc('\n', ttyout);
848 				break;
849 			}
850 			w = strlen(p);
851 			while (w < width) {
852 				w = (w + 8) &~ 7;
853 				(void)putc('\t', ttyout);
854 			}
855 		}
856 	}
857 }
858 
859 /*
860  * Update the global ttywidth value, using TIOCGWINSZ.
861  */
862 void
863 setttywidth(int a)
864 {
865 	struct winsize winsize;
866 	int oerrno = errno;
867 
868 	if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1 &&
869 	    winsize.ws_col != 0)
870 		ttywidth = winsize.ws_col;
871 	else
872 		ttywidth = 80;
873 	errno = oerrno;
874 }
875 
876 /*
877  * Change the rate limit up (SIGUSR1) or down (SIGUSR2)
878  */
879 void
880 crankrate(int sig)
881 {
882 
883 	switch (sig) {
884 	case SIGUSR1:
885 		if (rate_get)
886 			rate_get += rate_get_incr;
887 		if (rate_put)
888 			rate_put += rate_put_incr;
889 		break;
890 	case SIGUSR2:
891 		if (rate_get && rate_get > rate_get_incr)
892 			rate_get -= rate_get_incr;
893 		if (rate_put && rate_put > rate_put_incr)
894 			rate_put -= rate_put_incr;
895 		break;
896 	default:
897 		err(1, "crankrate invoked with unknown signal: %d", sig);
898 	}
899 }
900 
901 
902 /*
903  * Setup or cleanup EditLine structures
904  */
905 #ifndef NO_EDITCOMPLETE
906 void
907 controlediting(void)
908 {
909 	if (editing && el == NULL && hist == NULL) {
910 		HistEvent ev;
911 		int editmode;
912 
913 		el = el_init(getprogname(), stdin, ttyout, stderr);
914 		/* init editline */
915 		hist = history_init();		/* init the builtin history */
916 		history(hist, &ev, H_SETSIZE, 100);/* remember 100 events */
917 		el_set(el, EL_HIST, history, hist);	/* use history */
918 
919 		el_set(el, EL_EDITOR, "emacs");	/* default editor is emacs */
920 		el_set(el, EL_PROMPT, prompt);	/* set the prompt functions */
921 		el_set(el, EL_RPROMPT, rprompt);
922 
923 		/* add local file completion, bind to TAB */
924 		el_set(el, EL_ADDFN, "ftp-complete",
925 		    "Context sensitive argument completion",
926 		    complete);
927 		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
928 		el_source(el, NULL);	/* read ~/.editrc */
929 		if ((el_get(el, EL_EDITMODE, &editmode) != -1) && editmode == 0)
930 			editing = 0;	/* the user doesn't want editing,
931 					 * so disable, and let statement
932 					 * below cleanup */
933 		else
934 			el_set(el, EL_SIGNAL, 1);
935 	}
936 	if (!editing) {
937 		if (hist) {
938 			history_end(hist);
939 			hist = NULL;
940 		}
941 		if (el) {
942 			el_end(el);
943 			el = NULL;
944 		}
945 	}
946 }
947 #endif /* !NO_EDITCOMPLETE */
948 
949 /*
950  * Convert the string `arg' to an int, which may have an optional SI suffix
951  * (`b', `k', `m', `g'). Returns the number for success, -1 otherwise.
952  */
953 int
954 strsuftoi(const char *arg)
955 {
956 	char *cp;
957 	long val;
958 
959 	if (!isdigit((unsigned char)arg[0]))
960 		return (-1);
961 
962 	val = strtol(arg, &cp, 10);
963 	if (cp != NULL) {
964 		if (cp[0] != '\0' && cp[1] != '\0')
965 			 return (-1);
966 		switch (tolower((unsigned char)cp[0])) {
967 		case '\0':
968 		case 'b':
969 			break;
970 		case 'k':
971 			val <<= 10;
972 			break;
973 		case 'm':
974 			val <<= 20;
975 			break;
976 		case 'g':
977 			val <<= 30;
978 			break;
979 		default:
980 			return (-1);
981 		}
982 	}
983 	if (val < 0 || val > INT_MAX)
984 		return (-1);
985 
986 	return (val);
987 }
988 
989 /*
990  * Set up socket buffer sizes before a connection is made.
991  */
992 void
993 setupsockbufsize(int sock)
994 {
995 
996 	if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *) &sndbuf_size,
997 	    sizeof(rcvbuf_size)) < 0)
998 		warn("unable to set sndbuf size %d", sndbuf_size);
999 
1000 	if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *) &rcvbuf_size,
1001 	    sizeof(rcvbuf_size)) < 0)
1002 		warn("unable to set rcvbuf size %d", rcvbuf_size);
1003 }
1004 
1005 /*
1006  * Copy characters from src into dst, \ quoting characters that require it
1007  */
1008 void
1009 ftpvis(char *dst, size_t dstlen, const char *src, size_t srclen)
1010 {
1011 	int	di, si;
1012 
1013 	for (di = si = 0;
1014 	    src[si] != '\0' && di < dstlen && si < srclen;
1015 	    di++, si++) {
1016 		switch (src[si]) {
1017 		case '\\':
1018 		case ' ':
1019 		case '\t':
1020 		case '\r':
1021 		case '\n':
1022 		case '"':
1023 			dst[di++] = '\\';
1024 			if (di >= dstlen)
1025 				break;
1026 			/* FALLTHROUGH */
1027 		default:
1028 			dst[di] = src[si];
1029 		}
1030 	}
1031 	dst[di] = '\0';
1032 }
1033 
1034 /*
1035  * Copy src into buf (which is len bytes long), expanding % sequences.
1036  */
1037 void
1038 formatbuf(char *buf, size_t len, const char *src)
1039 {
1040 	const char	*p;
1041 	char		*p2, *q;
1042 	int		 i, op, updirs, pdirs;
1043 
1044 #define ADDBUF(x) do { \
1045 		if (i >= len - 1) \
1046 			goto endbuf; \
1047 		buf[i++] = (x); \
1048 	} while (0)
1049 
1050 	p = src;
1051 	for (i = 0; *p; p++) {
1052 		if (*p != '%') {
1053 			ADDBUF(*p);
1054 			continue;
1055 		}
1056 		p++;
1057 
1058 		switch (op = *p) {
1059 
1060 		case '/':
1061 		case '.':
1062 		case 'c':
1063 			p2 = connected ? remotepwd : "";
1064 			updirs = pdirs = 0;
1065 
1066 			/* option to determine fixed # of dirs from path */
1067 			if (op == '.' || op == 'c') {
1068 				int skip;
1069 
1070 				q = p2;
1071 				while (*p2)		/* calc # of /'s */
1072 					if (*p2++ == '/')
1073 						updirs++;
1074 				if (p[1] == '0') {	/* print <x> or ... */
1075 					pdirs = 1;
1076 					p++;
1077 				}
1078 				if (p[1] >= '1' && p[1] <= '9') {
1079 							/* calc # to skip  */
1080 					skip = p[1] - '0';
1081 					p++;
1082 				} else
1083 					skip = 1;
1084 
1085 				updirs -= skip;
1086 				while (skip-- > 0) {
1087 					while ((p2 > q) && (*p2 != '/'))
1088 						p2--;	/* back up */
1089 					if (skip && p2 > q)
1090 						p2--;
1091 				}
1092 				if (*p2 == '/' && p2 != q)
1093 					p2++;
1094 			}
1095 
1096 			if (updirs > 0 && pdirs) {
1097 				if (i >= len - 5)
1098 					break;
1099 				if (op == '.') {
1100 					ADDBUF('.');
1101 					ADDBUF('.');
1102 					ADDBUF('.');
1103 				} else {
1104 					ADDBUF('/');
1105 					ADDBUF('<');
1106 					if (updirs > 9) {
1107 						ADDBUF('9');
1108 						ADDBUF('+');
1109 					} else
1110 						ADDBUF('0' + updirs);
1111 					ADDBUF('>');
1112 				}
1113 			}
1114 			for (; *p2; p2++)
1115 				ADDBUF(*p2);
1116 			break;
1117 
1118 		case 'M':
1119 		case 'm':
1120 			for (p2 = connected && username ? username : "-";
1121 			    *p2 ; p2++) {
1122 				if (op == 'm' && *p2 == '.')
1123 					break;
1124 				ADDBUF(*p2);
1125 			}
1126 			break;
1127 
1128 		case 'n':
1129 			for (p2 = connected ? username : "-"; *p2 ; p2++)
1130 				ADDBUF(*p2);
1131 			break;
1132 
1133 		case '%':
1134 			ADDBUF('%');
1135 			break;
1136 
1137 		default:		/* display unknown codes literally */
1138 			ADDBUF('%');
1139 			ADDBUF(op);
1140 			break;
1141 
1142 		}
1143 	}
1144  endbuf:
1145 	buf[i] = '\0';
1146 }
1147 
1148 /*
1149  * Parse `port' into a TCP port number, defaulting to `defport' if `port' is
1150  * an unknown service name. If defport != -1, print a warning upon bad parse.
1151  */
1152 int
1153 parseport(const char *port, int defport)
1154 {
1155 	int	 rv;
1156 	long	 nport;
1157 	char	*p, *ep;
1158 
1159 	p = xstrdup(port);
1160 	nport = strtol(p, &ep, 10);
1161 	if (*ep != '\0' && ep == p) {
1162 		struct servent	*svp;
1163 
1164 		svp = getservbyname(port, "tcp");
1165 		if (svp == NULL) {
1166  badparseport:
1167 			if (defport != -1)
1168 				warnx("Unknown port `%s', using port %d",
1169 				    port, defport);
1170 			rv = defport;
1171 		} else
1172 			rv = ntohs(svp->s_port);
1173 	} else if (nport < 1 || nport > MAX_IN_PORT_T || *ep != '\0')
1174 		goto badparseport;
1175 	else
1176 		rv = nport;
1177 	free(p);
1178 	return (rv);
1179 }
1180 
1181 /*
1182  * Determine if given string is an IPv6 address or not.
1183  * Return 1 for yes, 0 for no
1184  */
1185 int
1186 isipv6addr(const char *addr)
1187 {
1188 	int rv = 0;
1189 #ifdef INET6
1190 	struct addrinfo hints, *res;
1191 
1192 	memset(&hints, 0, sizeof(hints));
1193 	hints.ai_family = PF_INET6;
1194 	hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
1195 	hints.ai_flags = AI_NUMERICHOST;
1196 	if (getaddrinfo(addr, "0", &hints, &res) != 0)
1197 		rv = 0;
1198 	else {
1199 		rv = 1;
1200 		freeaddrinfo(res);
1201 	}
1202 	if (debug)
1203 		fprintf(ttyout, "isipv6addr: got %d for %s\n", rv, addr);
1204 #endif
1205 	return (rv == 1) ? 1 : 0;
1206 }
1207 
1208 
1209 /*
1210  * Internal version of connect(2); sets socket buffer sizes first.
1211  */
1212 int
1213 xconnect(int sock, const struct sockaddr *name, int namelen)
1214 {
1215 
1216 	setupsockbufsize(sock);
1217 	return (connect(sock, name, namelen));
1218 }
1219 
1220 /*
1221  * Internal version of listen(2); sets socket buffer sizes first.
1222  */
1223 int
1224 xlisten(int sock, int backlog)
1225 {
1226 
1227 	setupsockbufsize(sock);
1228 	return (listen(sock, backlog));
1229 }
1230 
1231 /*
1232  * malloc() with inbuilt error checking
1233  */
1234 void *
1235 xmalloc(size_t size)
1236 {
1237 	void *p;
1238 
1239 	p = malloc(size);
1240 	if (p == NULL)
1241 		err(1, "Unable to allocate %ld bytes of memory", (long)size);
1242 	return (p);
1243 }
1244 
1245 /*
1246  * sl_init() with inbuilt error checking
1247  */
1248 StringList *
1249 xsl_init(void)
1250 {
1251 	StringList *p;
1252 
1253 	p = sl_init();
1254 	if (p == NULL)
1255 		err(1, "Unable to allocate memory for stringlist");
1256 	return (p);
1257 }
1258 
1259 /*
1260  * sl_add() with inbuilt error checking
1261  */
1262 void
1263 xsl_add(StringList *sl, char *i)
1264 {
1265 
1266 	if (sl_add(sl, i) == -1)
1267 		err(1, "Unable to add `%s' to stringlist", i);
1268 }
1269 
1270 /*
1271  * strdup() with inbuilt error checking
1272  */
1273 char *
1274 xstrdup(const char *str)
1275 {
1276 	char *s;
1277 
1278 	if (str == NULL)
1279 		errx(1, "xstrdup() called with NULL argument");
1280 	s = strdup(str);
1281 	if (s == NULL)
1282 		err(1, "Unable to allocate memory for string copy");
1283 	return (s);
1284 }
1285