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