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