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