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