1223328Sgavin/* $NetBSD: util.c,v 1.21 2009/11/15 10:12:37 lukem Exp $ */ 2223328Sgavin/* from NetBSD: util.c,v 1.152 2009/07/13 19:05:41 roy Exp */ 379971Sobrien 479971Sobrien/*- 5223328Sgavin * Copyright (c) 1997-2009 The NetBSD Foundation, Inc. 679971Sobrien * All rights reserved. 779971Sobrien * 879971Sobrien * This code is derived from software contributed to The NetBSD Foundation 979971Sobrien * by Luke Mewburn. 1079971Sobrien * 1179971Sobrien * This code is derived from software contributed to The NetBSD Foundation 1279971Sobrien * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility, 1379971Sobrien * NASA Ames Research Center. 1479971Sobrien * 1579971Sobrien * Redistribution and use in source and binary forms, with or without 1679971Sobrien * modification, are permitted provided that the following conditions 1779971Sobrien * are met: 1879971Sobrien * 1. Redistributions of source code must retain the above copyright 1979971Sobrien * notice, this list of conditions and the following disclaimer. 2079971Sobrien * 2. Redistributions in binary form must reproduce the above copyright 2179971Sobrien * notice, this list of conditions and the following disclaimer in the 2279971Sobrien * documentation and/or other materials provided with the distribution. 2379971Sobrien * 2479971Sobrien * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 2579971Sobrien * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 2679971Sobrien * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 2779971Sobrien * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 2879971Sobrien * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 2979971Sobrien * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 3079971Sobrien * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 3179971Sobrien * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 3279971Sobrien * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 3379971Sobrien * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 3479971Sobrien * POSSIBILITY OF SUCH DAMAGE. 3579971Sobrien */ 3679971Sobrien 3779971Sobrien/* 3879971Sobrien * Copyright (c) 1985, 1989, 1993, 1994 3979971Sobrien * The Regents of the University of California. All rights reserved. 4079971Sobrien * 4179971Sobrien * Redistribution and use in source and binary forms, with or without 4279971Sobrien * modification, are permitted provided that the following conditions 4379971Sobrien * are met: 4479971Sobrien * 1. Redistributions of source code must retain the above copyright 4579971Sobrien * notice, this list of conditions and the following disclaimer. 4679971Sobrien * 2. Redistributions in binary form must reproduce the above copyright 4779971Sobrien * notice, this list of conditions and the following disclaimer in the 4879971Sobrien * documentation and/or other materials provided with the distribution. 49121966Smikeh * 3. Neither the name of the University nor the names of its contributors 5079971Sobrien * may be used to endorse or promote products derived from this software 5179971Sobrien * without specific prior written permission. 5279971Sobrien * 5379971Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 5479971Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 5579971Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 5679971Sobrien * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 5779971Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 5879971Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 5979971Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 6079971Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 6179971Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 6279971Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 6379971Sobrien * SUCH DAMAGE. 6479971Sobrien */ 6579971Sobrien 66223328Sgavin#include "tnftp.h" 67223328Sgavin 68223328Sgavin#if 0 /* tnftp */ 69223328Sgavin 70116424Smikeh#include <sys/cdefs.h> 71116424Smikeh#ifndef lint 72223328Sgavin__RCSID(" NetBSD: util.c,v 1.152 2009/07/13 19:05:41 roy Exp "); 73116424Smikeh#endif /* not lint */ 74116424Smikeh 7579971Sobrien/* 7679971Sobrien * FTP User Program -- Misc support routines 7779971Sobrien */ 78142129Smikeh#include <sys/param.h> 79116424Smikeh#include <sys/socket.h> 80116424Smikeh#include <sys/ioctl.h> 81116424Smikeh#include <sys/time.h> 82116424Smikeh#include <netinet/in.h> 83116424Smikeh#include <arpa/ftp.h> 8479971Sobrien 85116424Smikeh#include <ctype.h> 86116424Smikeh#include <err.h> 87116424Smikeh#include <errno.h> 88116424Smikeh#include <fcntl.h> 89116424Smikeh#include <glob.h> 90116424Smikeh#include <signal.h> 91223328Sgavin#include <libgen.h> 92116424Smikeh#include <limits.h> 93116424Smikeh#include <netdb.h> 94116424Smikeh#include <stdio.h> 95116424Smikeh#include <stdlib.h> 96116424Smikeh#include <string.h> 97116424Smikeh#include <termios.h> 98116424Smikeh#include <time.h> 99223328Sgavin#include <tzfile.h> 100116424Smikeh#include <unistd.h> 10179971Sobrien 102223328Sgavin#endif /* tnftp */ 103223328Sgavin 10479971Sobrien#include "ftp_var.h" 10579971Sobrien 10679971Sobrien/* 10779971Sobrien * Connect to peer server and auto-login, if possible. 10879971Sobrien */ 10979971Sobrienvoid 11079971Sobriensetpeer(int argc, char *argv[]) 11179971Sobrien{ 11279971Sobrien char *host; 113223328Sgavin const char *port; 11479971Sobrien 11579971Sobrien if (argc == 0) 11679971Sobrien goto usage; 11779971Sobrien if (connected) { 11879971Sobrien fprintf(ttyout, "Already connected to %s, use close first.\n", 11979971Sobrien hostname); 12079971Sobrien code = -1; 12179971Sobrien return; 12279971Sobrien } 12379971Sobrien if (argc < 2) 12479971Sobrien (void)another(&argc, &argv, "to"); 12579971Sobrien if (argc < 2 || argc > 3) { 12679971Sobrien usage: 127223328Sgavin UPRINTF("usage: %s host-name [port]\n", argv[0]); 12879971Sobrien code = -1; 12979971Sobrien return; 13079971Sobrien } 13179971Sobrien if (gatemode) 13279971Sobrien port = gateport; 13379971Sobrien else 13479971Sobrien port = ftpport; 13579971Sobrien if (argc > 2) 13679971Sobrien port = argv[2]; 13779971Sobrien 13879971Sobrien if (gatemode) { 13979971Sobrien if (gateserver == NULL || *gateserver == '\0') 140223328Sgavin errx(1, "main: gateserver not defined"); 14179971Sobrien host = hookup(gateserver, port); 14279971Sobrien } else 14379971Sobrien host = hookup(argv[1], port); 14479971Sobrien 14579971Sobrien if (host) { 14679971Sobrien if (gatemode && verbose) { 14779971Sobrien fprintf(ttyout, 14879971Sobrien "Connecting via pass-through server %s\n", 14979971Sobrien gateserver); 15079971Sobrien } 15179971Sobrien 15279971Sobrien connected = 1; 15379971Sobrien /* 15479971Sobrien * Set up defaults for FTP. 15579971Sobrien */ 15679971Sobrien (void)strlcpy(typename, "ascii", sizeof(typename)); 15779971Sobrien type = TYPE_A; 15879971Sobrien curtype = TYPE_A; 15979971Sobrien (void)strlcpy(formname, "non-print", sizeof(formname)); 16079971Sobrien form = FORM_N; 16179971Sobrien (void)strlcpy(modename, "stream", sizeof(modename)); 16279971Sobrien mode = MODE_S; 16379971Sobrien (void)strlcpy(structname, "file", sizeof(structname)); 16479971Sobrien stru = STRU_F; 16579971Sobrien (void)strlcpy(bytename, "8", sizeof(bytename)); 16679971Sobrien bytesize = 8; 16779971Sobrien if (autologin) 16879971Sobrien (void)ftp_login(argv[1], NULL, NULL); 16979971Sobrien } 17079971Sobrien} 17179971Sobrien 17279971Sobrienstatic void 173223328Sgavinparse_feat(const char *fline) 17479971Sobrien{ 17579971Sobrien 176121966Smikeh /* 177121966Smikeh * work-around broken ProFTPd servers that can't 178223328Sgavin * even obey RFC2389. 179121966Smikeh */ 180223328Sgavin while (*fline && isspace((int)*fline)) 181223328Sgavin fline++; 182121966Smikeh 183223328Sgavin if (strcasecmp(fline, "MDTM") == 0) 18479971Sobrien features[FEAT_MDTM] = 1; 185223328Sgavin else if (strncasecmp(fline, "MLST", sizeof("MLST") - 1) == 0) { 18679971Sobrien features[FEAT_MLST] = 1; 187223328Sgavin } else if (strcasecmp(fline, "REST STREAM") == 0) 18879971Sobrien features[FEAT_REST_STREAM] = 1; 189223328Sgavin else if (strcasecmp(fline, "SIZE") == 0) 19079971Sobrien features[FEAT_SIZE] = 1; 191223328Sgavin else if (strcasecmp(fline, "TVFS") == 0) 19279971Sobrien features[FEAT_TVFS] = 1; 19379971Sobrien} 19479971Sobrien 19579971Sobrien/* 19679971Sobrien * Determine the remote system type (SYST) and features (FEAT). 19779971Sobrien * Call after a successful login (i.e, connected = -1) 19879971Sobrien */ 19979971Sobrienvoid 20079971Sobriengetremoteinfo(void) 20179971Sobrien{ 20279971Sobrien int overbose, i; 20379971Sobrien 20479971Sobrien overbose = verbose; 205223328Sgavin if (ftp_debug == 0) 20679971Sobrien verbose = -1; 20779971Sobrien 20879971Sobrien /* determine remote system type */ 20979971Sobrien if (command("SYST") == COMPLETE) { 21079971Sobrien if (overbose) { 21179971Sobrien char *cp, c; 21279971Sobrien 21379971Sobrien c = 0; 21479971Sobrien cp = strchr(reply_string + 4, ' '); 21579971Sobrien if (cp == NULL) 21679971Sobrien cp = strchr(reply_string + 4, '\r'); 21779971Sobrien if (cp) { 21879971Sobrien if (cp[-1] == '.') 21979971Sobrien cp--; 22079971Sobrien c = *cp; 22179971Sobrien *cp = '\0'; 22279971Sobrien } 22379971Sobrien 22479971Sobrien fprintf(ttyout, "Remote system type is %s.\n", 22579971Sobrien reply_string + 4); 22679971Sobrien if (cp) 22779971Sobrien *cp = c; 22879971Sobrien } 22979971Sobrien if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) { 23079971Sobrien if (proxy) 23179971Sobrien unix_proxy = 1; 23279971Sobrien else 23379971Sobrien unix_server = 1; 23479971Sobrien /* 23579971Sobrien * Set type to 0 (not specified by user), 23679971Sobrien * meaning binary by default, but don't bother 23779971Sobrien * telling server. We can use binary 23879971Sobrien * for text files unless changed by the user. 23979971Sobrien */ 24079971Sobrien type = 0; 24179971Sobrien (void)strlcpy(typename, "binary", sizeof(typename)); 24279971Sobrien if (overbose) 24379971Sobrien fprintf(ttyout, 24479971Sobrien "Using %s mode to transfer files.\n", 24579971Sobrien typename); 24679971Sobrien } else { 24779971Sobrien if (proxy) 24879971Sobrien unix_proxy = 0; 24979971Sobrien else 25079971Sobrien unix_server = 0; 25179971Sobrien if (overbose && 25279971Sobrien !strncmp(reply_string, "215 TOPS20", 10)) 25379971Sobrien fputs( 25479971Sobrien"Remember to set tenex mode when transferring binary files from this machine.\n", 25579971Sobrien ttyout); 25679971Sobrien } 25779971Sobrien } 25879971Sobrien 25979971Sobrien /* determine features (if any) */ 26079971Sobrien for (i = 0; i < FEAT_max; i++) 26179971Sobrien features[i] = -1; 26279971Sobrien reply_callback = parse_feat; 26379971Sobrien if (command("FEAT") == COMPLETE) { 26479971Sobrien for (i = 0; i < FEAT_max; i++) { 26579971Sobrien if (features[i] == -1) 26679971Sobrien features[i] = 0; 26779971Sobrien } 26879971Sobrien features[FEAT_FEAT] = 1; 26979971Sobrien } else 27079971Sobrien features[FEAT_FEAT] = 0; 271223328Sgavin#ifndef NO_DEBUG 272223328Sgavin if (ftp_debug) { 273121966Smikeh#define DEBUG_FEAT(x) fprintf(ttyout, "features[" #x "] = %d\n", features[(x)]) 274121966Smikeh DEBUG_FEAT(FEAT_FEAT); 275121966Smikeh DEBUG_FEAT(FEAT_MDTM); 276121966Smikeh DEBUG_FEAT(FEAT_MLST); 277121966Smikeh DEBUG_FEAT(FEAT_REST_STREAM); 278121966Smikeh DEBUG_FEAT(FEAT_SIZE); 279121966Smikeh DEBUG_FEAT(FEAT_TVFS); 280121966Smikeh#undef DEBUG_FEAT 281121966Smikeh } 282223328Sgavin#endif 28379971Sobrien reply_callback = NULL; 28479971Sobrien 28579971Sobrien verbose = overbose; 28679971Sobrien} 28779971Sobrien 28879971Sobrien/* 28979971Sobrien * Reset the various variables that indicate connection state back to 29079971Sobrien * disconnected settings. 29179971Sobrien * The caller is responsible for issuing any commands to the remote server 29279971Sobrien * to perform a clean shutdown before this is invoked. 29379971Sobrien */ 29479971Sobrienvoid 29579971Sobriencleanuppeer(void) 29679971Sobrien{ 29779971Sobrien 29879971Sobrien if (cout) 29979971Sobrien (void)fclose(cout); 30079971Sobrien cout = NULL; 30179971Sobrien connected = 0; 30279971Sobrien unix_server = 0; 30379971Sobrien unix_proxy = 0; 30479971Sobrien /* 30579971Sobrien * determine if anonftp was specifically set with -a 30679971Sobrien * (1), or implicitly set by auto_fetch() (2). in the 30779971Sobrien * latter case, disable after the current xfer 30879971Sobrien */ 30979971Sobrien if (anonftp == 2) 31079971Sobrien anonftp = 0; 31179971Sobrien data = -1; 31279971Sobrien epsv4bad = 0; 313223328Sgavin epsv6bad = 0; 31479971Sobrien if (username) 31579971Sobrien free(username); 31679971Sobrien username = NULL; 31779971Sobrien if (!proxy) 31879971Sobrien macnum = 0; 31979971Sobrien} 32079971Sobrien 32179971Sobrien/* 32279971Sobrien * Top-level signal handler for interrupted commands. 32379971Sobrien */ 32479971Sobrienvoid 325142129Smikehintr(int signo) 32679971Sobrien{ 32779971Sobrien 328142129Smikeh sigint_raised = 1; 32979971Sobrien alarmtimer(0); 33079971Sobrien if (fromatty) 33179971Sobrien write(fileno(ttyout), "\n", 1); 33279971Sobrien siglongjmp(toplevel, 1); 33379971Sobrien} 33479971Sobrien 33579971Sobrien/* 33679971Sobrien * Signal handler for lost connections; cleanup various elements of 33779971Sobrien * the connection state, and call cleanuppeer() to finish it off. 33879971Sobrien */ 33979971Sobrienvoid 34079971Sobrienlostpeer(int dummy) 34179971Sobrien{ 34279971Sobrien int oerrno = errno; 34379971Sobrien 34479971Sobrien alarmtimer(0); 34579971Sobrien if (connected) { 34679971Sobrien if (cout != NULL) { 34779971Sobrien (void)shutdown(fileno(cout), 1+1); 34879971Sobrien (void)fclose(cout); 34979971Sobrien cout = NULL; 35079971Sobrien } 35179971Sobrien if (data >= 0) { 35279971Sobrien (void)shutdown(data, 1+1); 35379971Sobrien (void)close(data); 35479971Sobrien data = -1; 35579971Sobrien } 35679971Sobrien connected = 0; 35779971Sobrien } 35879971Sobrien pswitch(1); 35979971Sobrien if (connected) { 36079971Sobrien if (cout != NULL) { 36179971Sobrien (void)shutdown(fileno(cout), 1+1); 36279971Sobrien (void)fclose(cout); 36379971Sobrien cout = NULL; 36479971Sobrien } 36579971Sobrien connected = 0; 36679971Sobrien } 36779971Sobrien proxflag = 0; 36879971Sobrien pswitch(0); 36979971Sobrien cleanuppeer(); 37079971Sobrien errno = oerrno; 37179971Sobrien} 37279971Sobrien 37379971Sobrien 37479971Sobrien/* 37579971Sobrien * Login to remote host, using given username & password if supplied. 37679971Sobrien * Return non-zero if successful. 37779971Sobrien */ 37879971Sobrienint 379223328Sgavinftp_login(const char *host, const char *luser, const char *lpass) 38079971Sobrien{ 38179971Sobrien char tmp[80]; 382223328Sgavin char *fuser, *pass, *facct, *p; 383223328Sgavin char emptypass[] = ""; 384223328Sgavin const char *errormsg; 385223328Sgavin int n, aflag, rval, nlen; 38679971Sobrien 387223328Sgavin aflag = rval = 0; 388223328Sgavin fuser = pass = facct = NULL; 389223328Sgavin if (luser) 390223328Sgavin fuser = ftp_strdup(luser); 391223328Sgavin if (lpass) 392223328Sgavin pass = ftp_strdup(lpass); 39379971Sobrien 394223328Sgavin DPRINTF("ftp_login: user `%s' pass `%s' host `%s'\n", 395223328Sgavin STRorNULL(fuser), STRorNULL(pass), STRorNULL(host)); 39679971Sobrien 39779971Sobrien /* 39879971Sobrien * Set up arguments for an anonymous FTP session, if necessary. 39979971Sobrien */ 40079971Sobrien if (anonftp) { 401223328Sgavin FREEPTR(fuser); 402223328Sgavin fuser = ftp_strdup("anonymous"); /* as per RFC1635 */ 403223328Sgavin FREEPTR(pass); 404223328Sgavin pass = ftp_strdup(getoptionvalue("anonpass")); 40579971Sobrien } 40679971Sobrien 407223328Sgavin if (ruserpass(host, &fuser, &pass, &facct) < 0) { 40879971Sobrien code = -1; 40979971Sobrien goto cleanup_ftp_login; 41079971Sobrien } 41179971Sobrien 412223328Sgavin while (fuser == NULL) { 41398247Smikeh if (localname) 41498247Smikeh fprintf(ttyout, "Name (%s:%s): ", host, localname); 41579971Sobrien else 41679971Sobrien fprintf(ttyout, "Name (%s): ", host); 417223328Sgavin errormsg = NULL; 418223328Sgavin nlen = get_line(stdin, tmp, sizeof(tmp), &errormsg); 419223328Sgavin if (nlen < 0) { 420223328Sgavin fprintf(ttyout, "%s; %s aborted.\n", errormsg, "login"); 42179971Sobrien code = -1; 42279971Sobrien goto cleanup_ftp_login; 423223328Sgavin } else if (nlen == 0) { 424223328Sgavin fuser = ftp_strdup(localname); 425223328Sgavin } else { 426223328Sgavin fuser = ftp_strdup(tmp); 42779971Sobrien } 42879971Sobrien } 42979971Sobrien 43079971Sobrien if (gatemode) { 43179971Sobrien char *nuser; 432223328Sgavin size_t len; 43379971Sobrien 434223328Sgavin len = strlen(fuser) + 1 + strlen(host) + 1; 435223328Sgavin nuser = ftp_malloc(len); 436223328Sgavin (void)strlcpy(nuser, fuser, len); 43779971Sobrien (void)strlcat(nuser, "@", len); 43879971Sobrien (void)strlcat(nuser, host, len); 439223328Sgavin FREEPTR(fuser); 440223328Sgavin fuser = nuser; 44179971Sobrien } 44279971Sobrien 443223328Sgavin n = command("USER %s", fuser); 44479971Sobrien if (n == CONTINUE) { 44579971Sobrien if (pass == NULL) { 446223328Sgavin p = getpass("Password: "); 447223328Sgavin if (p == NULL) 448223328Sgavin p = emptypass; 449223328Sgavin pass = ftp_strdup(p); 450223328Sgavin memset(p, 0, strlen(p)); 45179971Sobrien } 45279971Sobrien n = command("PASS %s", pass); 453223328Sgavin memset(pass, 0, strlen(pass)); 45479971Sobrien } 45579971Sobrien if (n == CONTINUE) { 45679971Sobrien aflag++; 457223328Sgavin if (facct == NULL) { 458223328Sgavin p = getpass("Account: "); 459223328Sgavin if (p == NULL) 460223328Sgavin p = emptypass; 461223328Sgavin facct = ftp_strdup(p); 462223328Sgavin memset(p, 0, strlen(p)); 46379971Sobrien } 464223328Sgavin if (facct[0] == '\0') { 465223328Sgavin warnx("Login failed"); 46679971Sobrien goto cleanup_ftp_login; 46779971Sobrien } 468223328Sgavin n = command("ACCT %s", facct); 469223328Sgavin memset(facct, 0, strlen(facct)); 47079971Sobrien } 47179971Sobrien if ((n != COMPLETE) || 472223328Sgavin (!aflag && facct != NULL && command("ACCT %s", facct) != COMPLETE)) { 473223328Sgavin warnx("Login failed"); 47479971Sobrien goto cleanup_ftp_login; 47579971Sobrien } 47679971Sobrien rval = 1; 477223328Sgavin username = ftp_strdup(fuser); 47879971Sobrien if (proxy) 47979971Sobrien goto cleanup_ftp_login; 48079971Sobrien 48179971Sobrien connected = -1; 48279971Sobrien getremoteinfo(); 48379971Sobrien for (n = 0; n < macnum; ++n) { 48479971Sobrien if (!strcmp("init", macros[n].mac_name)) { 48579971Sobrien (void)strlcpy(line, "$init", sizeof(line)); 48679971Sobrien makeargv(); 48779971Sobrien domacro(margc, margv); 48879971Sobrien break; 48979971Sobrien } 49079971Sobrien } 491142129Smikeh updatelocalcwd(); 492142129Smikeh updateremotecwd(); 49379971Sobrien 49479971Sobrien cleanup_ftp_login: 495223328Sgavin FREEPTR(fuser); 496223328Sgavin if (pass != NULL) 497223328Sgavin memset(pass, 0, strlen(pass)); 498223328Sgavin FREEPTR(pass); 499223328Sgavin if (facct != NULL) 500223328Sgavin memset(facct, 0, strlen(facct)); 501223328Sgavin FREEPTR(facct); 50279971Sobrien return (rval); 50379971Sobrien} 50479971Sobrien 50579971Sobrien/* 50679971Sobrien * `another' gets another argument, and stores the new argc and argv. 50779971Sobrien * It reverts to the top level (via intr()) on EOF/error. 50879971Sobrien * 50979971Sobrien * Returns false if no new arguments have been added. 51079971Sobrien */ 51179971Sobrienint 512223328Sgavinanother(int *pargc, char ***pargv, const char *aprompt) 51379971Sobrien{ 514223328Sgavin const char *errormsg; 515223328Sgavin int ret, nlen; 516223328Sgavin size_t len; 51779971Sobrien 518223328Sgavin len = strlen(line); 51979971Sobrien if (len >= sizeof(line) - 3) { 520223328Sgavin fputs("Sorry, arguments too long.\n", ttyout); 52179971Sobrien intr(0); 52279971Sobrien } 523223328Sgavin fprintf(ttyout, "(%s) ", aprompt); 52479971Sobrien line[len++] = ' '; 525223328Sgavin errormsg = NULL; 526223328Sgavin nlen = get_line(stdin, line + len, sizeof(line)-len, &errormsg); 527223328Sgavin if (nlen < 0) { 528223328Sgavin fprintf(ttyout, "%s; %s aborted.\n", errormsg, "operation"); 52979971Sobrien intr(0); 53079971Sobrien } 531223328Sgavin len += nlen; 53279971Sobrien makeargv(); 53379971Sobrien ret = margc > *pargc; 53479971Sobrien *pargc = margc; 53579971Sobrien *pargv = margv; 53679971Sobrien return (ret); 53779971Sobrien} 53879971Sobrien 53979971Sobrien/* 54079971Sobrien * glob files given in argv[] from the remote server. 54179971Sobrien * if errbuf isn't NULL, store error messages there instead 54279971Sobrien * of writing to the screen. 54379971Sobrien */ 54479971Sobrienchar * 545223328Sgavinremglob(char *argv[], int doswitch, const char **errbuf) 54679971Sobrien{ 547223328Sgavin static char buf[MAXPATHLEN]; 548223328Sgavin static FILE *ftemp = NULL; 549223328Sgavin static char **args; 550223328Sgavin char temp[MAXPATHLEN]; 551223328Sgavin int oldverbose, oldhash, oldprogress, fd; 552223328Sgavin char *cp; 553223328Sgavin const char *rmode; 554223328Sgavin size_t len; 55579971Sobrien 556223328Sgavin if (!mflag || !connected) { 557223328Sgavin if (!doglob) 558223328Sgavin args = NULL; 559223328Sgavin else { 560223328Sgavin if (ftemp) { 561223328Sgavin (void)fclose(ftemp); 562223328Sgavin ftemp = NULL; 563223328Sgavin } 564223328Sgavin } 565223328Sgavin return (NULL); 566223328Sgavin } 567223328Sgavin if (!doglob) { 568223328Sgavin if (args == NULL) 569223328Sgavin args = argv; 570223328Sgavin if ((cp = *++args) == NULL) 571223328Sgavin args = NULL; 572223328Sgavin return (cp); 573223328Sgavin } 574223328Sgavin if (ftemp == NULL) { 57579971Sobrien len = strlcpy(temp, tmpdir, sizeof(temp)); 57679971Sobrien if (temp[len - 1] != '/') 57779971Sobrien (void)strlcat(temp, "/", sizeof(temp)); 57879971Sobrien (void)strlcat(temp, TMPFILE, sizeof(temp)); 579223328Sgavin if ((fd = mkstemp(temp)) < 0) { 580223328Sgavin warn("Unable to create temporary file `%s'", temp); 581223328Sgavin return (NULL); 582223328Sgavin } 583223328Sgavin close(fd); 584223328Sgavin oldverbose = verbose; 58579971Sobrien verbose = (errbuf != NULL) ? -1 : 0; 586223328Sgavin oldhash = hash; 58798247Smikeh oldprogress = progress; 588223328Sgavin hash = 0; 58998247Smikeh progress = 0; 590223328Sgavin if (doswitch) 591223328Sgavin pswitch(!proxy); 592223328Sgavin for (rmode = "w"; *++argv != NULL; rmode = "a") 593223328Sgavin recvrequest("NLST", temp, *argv, rmode, 0, 0); 59479971Sobrien if ((code / 100) != COMPLETE) { 59579971Sobrien if (errbuf != NULL) 59679971Sobrien *errbuf = reply_string; 59779971Sobrien } 598223328Sgavin if (doswitch) 599223328Sgavin pswitch(!proxy); 600223328Sgavin verbose = oldverbose; 60179971Sobrien hash = oldhash; 60298247Smikeh progress = oldprogress; 603223328Sgavin ftemp = fopen(temp, "r"); 604223328Sgavin (void)unlink(temp); 605223328Sgavin if (ftemp == NULL) { 60679971Sobrien if (errbuf == NULL) 607223328Sgavin warnx("Can't find list of remote files"); 60879971Sobrien else 60979971Sobrien *errbuf = 610223328Sgavin "Can't find list of remote files"; 611223328Sgavin return (NULL); 612223328Sgavin } 613223328Sgavin } 614223328Sgavin if (fgets(buf, sizeof(buf), ftemp) == NULL) { 615223328Sgavin (void)fclose(ftemp); 61679971Sobrien ftemp = NULL; 617223328Sgavin return (NULL); 618223328Sgavin } 619223328Sgavin if ((cp = strchr(buf, '\n')) != NULL) 620223328Sgavin *cp = '\0'; 621223328Sgavin return (buf); 62279971Sobrien} 62379971Sobrien 62479971Sobrien/* 62579971Sobrien * Glob a local file name specification with the expectation of a single 62679971Sobrien * return value. Can't control multiple values being expanded from the 62779971Sobrien * expression, we return only the first. 62879971Sobrien * Returns NULL on error, or a pointer to a buffer containing the filename 62979971Sobrien * that's the caller's responsiblity to free(3) when finished with. 63079971Sobrien */ 63179971Sobrienchar * 63279971Sobrienglobulize(const char *pattern) 63379971Sobrien{ 63479971Sobrien glob_t gl; 63579971Sobrien int flags; 63679971Sobrien char *p; 63779971Sobrien 63879971Sobrien if (!doglob) 639223328Sgavin return (ftp_strdup(pattern)); 64079971Sobrien 64179971Sobrien flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE; 64279971Sobrien memset(&gl, 0, sizeof(gl)); 64379971Sobrien if (glob(pattern, flags, NULL, &gl) || gl.gl_pathc == 0) { 644223328Sgavin warnx("Glob pattern `%s' not found", pattern); 64579971Sobrien globfree(&gl); 64679971Sobrien return (NULL); 64779971Sobrien } 648223328Sgavin p = ftp_strdup(gl.gl_pathv[0]); 64979971Sobrien globfree(&gl); 65079971Sobrien return (p); 65179971Sobrien} 65279971Sobrien 65379971Sobrien/* 65479971Sobrien * determine size of remote file 65579971Sobrien */ 65679971Sobrienoff_t 65779971Sobrienremotesize(const char *file, int noisy) 65879971Sobrien{ 65979971Sobrien int overbose, r; 66079971Sobrien off_t size; 66179971Sobrien 66279971Sobrien overbose = verbose; 66379971Sobrien size = -1; 664223328Sgavin if (ftp_debug == 0) 66579971Sobrien verbose = -1; 66679971Sobrien if (! features[FEAT_SIZE]) { 66779971Sobrien if (noisy) 66879971Sobrien fprintf(ttyout, 66979971Sobrien "SIZE is not supported by remote server.\n"); 67079971Sobrien goto cleanup_remotesize; 67179971Sobrien } 67279971Sobrien r = command("SIZE %s", file); 67379971Sobrien if (r == COMPLETE) { 67479971Sobrien char *cp, *ep; 67579971Sobrien 67679971Sobrien cp = strchr(reply_string, ' '); 67779971Sobrien if (cp != NULL) { 67879971Sobrien cp++; 67979971Sobrien size = STRTOLL(cp, &ep, 10); 68079971Sobrien if (*ep != '\0' && !isspace((unsigned char)*ep)) 68179971Sobrien size = -1; 68279971Sobrien } 68379971Sobrien } else { 68479971Sobrien if (r == ERROR && code == 500 && features[FEAT_SIZE] == -1) 68579971Sobrien features[FEAT_SIZE] = 0; 686223328Sgavin if (noisy && ftp_debug == 0) { 68779971Sobrien fputs(reply_string, ttyout); 68879971Sobrien putc('\n', ttyout); 68979971Sobrien } 69079971Sobrien } 69179971Sobrien cleanup_remotesize: 69279971Sobrien verbose = overbose; 69379971Sobrien return (size); 69479971Sobrien} 69579971Sobrien 69679971Sobrien/* 69779971Sobrien * determine last modification time (in GMT) of remote file 69879971Sobrien */ 69979971Sobrientime_t 70079971Sobrienremotemodtime(const char *file, int noisy) 70179971Sobrien{ 70279971Sobrien int overbose, ocode, r; 70379971Sobrien time_t rtime; 70479971Sobrien 70579971Sobrien overbose = verbose; 70679971Sobrien ocode = code; 70779971Sobrien rtime = -1; 708223328Sgavin if (ftp_debug == 0) 70979971Sobrien verbose = -1; 71079971Sobrien if (! features[FEAT_MDTM]) { 71179971Sobrien if (noisy) 71279971Sobrien fprintf(ttyout, 71379971Sobrien "MDTM is not supported by remote server.\n"); 71479971Sobrien goto cleanup_parse_time; 71579971Sobrien } 71679971Sobrien r = command("MDTM %s", file); 71779971Sobrien if (r == COMPLETE) { 71879971Sobrien struct tm timebuf; 71979971Sobrien char *timestr, *frac; 72079971Sobrien 72179971Sobrien /* 72279971Sobrien * time-val = 14DIGIT [ "." 1*DIGIT ] 72379971Sobrien * YYYYMMDDHHMMSS[.sss] 72479971Sobrien * mdtm-response = "213" SP time-val CRLF / error-response 72579971Sobrien */ 72679971Sobrien timestr = reply_string + 4; 72779971Sobrien 72879971Sobrien /* 72979971Sobrien * parse fraction. 73079971Sobrien * XXX: ignored for now 73179971Sobrien */ 73279971Sobrien frac = strchr(timestr, '\r'); 73379971Sobrien if (frac != NULL) 73479971Sobrien *frac = '\0'; 73579971Sobrien frac = strchr(timestr, '.'); 73679971Sobrien if (frac != NULL) 73779971Sobrien *frac++ = '\0'; 73879971Sobrien if (strlen(timestr) == 15 && strncmp(timestr, "191", 3) == 0) { 73979971Sobrien /* 74079971Sobrien * XXX: Workaround for lame ftpd's that return 74179971Sobrien * `19100' instead of `2000' 74279971Sobrien */ 74379971Sobrien fprintf(ttyout, 74479971Sobrien "Y2K warning! Incorrect time-val `%s' received from server.\n", 74579971Sobrien timestr); 74679971Sobrien timestr++; 74779971Sobrien timestr[0] = '2'; 74879971Sobrien timestr[1] = '0'; 74979971Sobrien fprintf(ttyout, "Converted to `%s'\n", timestr); 75079971Sobrien } 751223328Sgavin memset(&timebuf, 0, sizeof(timebuf)); 75279971Sobrien if (strlen(timestr) != 14 || 753223328Sgavin (strptime(timestr, "%Y%m%d%H%M%S", &timebuf) == NULL)) { 75479971Sobrien bad_parse_time: 75579971Sobrien fprintf(ttyout, "Can't parse time `%s'.\n", timestr); 75679971Sobrien goto cleanup_parse_time; 75779971Sobrien } 75879971Sobrien timebuf.tm_isdst = -1; 75979971Sobrien rtime = timegm(&timebuf); 76079971Sobrien if (rtime == -1) { 761223328Sgavin if (noisy || ftp_debug != 0) 76279971Sobrien goto bad_parse_time; 76379971Sobrien else 76479971Sobrien goto cleanup_parse_time; 765223328Sgavin } else { 766223328Sgavin DPRINTF("remotemodtime: parsed date `%s' as " LLF 767223328Sgavin ", %s", 768223328Sgavin timestr, (LLT)rtime, 769223328Sgavin rfc2822time(localtime(&rtime))); 770223328Sgavin } 77179971Sobrien } else { 77279971Sobrien if (r == ERROR && code == 500 && features[FEAT_MDTM] == -1) 77379971Sobrien features[FEAT_MDTM] = 0; 774223328Sgavin if (noisy && ftp_debug == 0) { 77579971Sobrien fputs(reply_string, ttyout); 77679971Sobrien putc('\n', ttyout); 77779971Sobrien } 77879971Sobrien } 77979971Sobrien cleanup_parse_time: 78079971Sobrien verbose = overbose; 78179971Sobrien if (rtime == -1) 78279971Sobrien code = ocode; 78379971Sobrien return (rtime); 78479971Sobrien} 78579971Sobrien 78679971Sobrien/* 787223328Sgavin * Format tm in an RFC2822 compatible manner, with a trailing \n. 788223328Sgavin * Returns a pointer to a static string containing the result. 789223328Sgavin */ 790223328Sgavinconst char * 791223328Sgavinrfc2822time(const struct tm *tm) 792223328Sgavin{ 793223328Sgavin static char result[50]; 794223328Sgavin 795223328Sgavin if (strftime(result, sizeof(result), 796223328Sgavin "%a, %d %b %Y %H:%M:%S %z\n", tm) == 0) 797223328Sgavin errx(1, "Can't convert RFC2822 time: buffer too small"); 798223328Sgavin return result; 799223328Sgavin} 800223328Sgavin 801223328Sgavin/* 802142129Smikeh * Update global `localcwd', which contains the state of the local cwd 80379971Sobrien */ 80479971Sobrienvoid 805142129Smikehupdatelocalcwd(void) 80679971Sobrien{ 807142129Smikeh 808142129Smikeh if (getcwd(localcwd, sizeof(localcwd)) == NULL) 809142129Smikeh localcwd[0] = '\0'; 810223328Sgavin DPRINTF("updatelocalcwd: got `%s'\n", localcwd); 811142129Smikeh} 812142129Smikeh 813142129Smikeh/* 814142129Smikeh * Update global `remotecwd', which contains the state of the remote cwd 815142129Smikeh */ 816142129Smikehvoid 817142129Smikehupdateremotecwd(void) 818142129Smikeh{ 819223328Sgavin int overbose, ocode; 820223328Sgavin size_t i; 82179971Sobrien char *cp; 82279971Sobrien 82379971Sobrien overbose = verbose; 82479971Sobrien ocode = code; 825223328Sgavin if (ftp_debug == 0) 82679971Sobrien verbose = -1; 82779971Sobrien if (command("PWD") != COMPLETE) 828142129Smikeh goto badremotecwd; 82979971Sobrien cp = strchr(reply_string, ' '); 83079971Sobrien if (cp == NULL || cp[0] == '\0' || cp[1] != '"') 831142129Smikeh goto badremotecwd; 83279971Sobrien cp += 2; 833142129Smikeh for (i = 0; *cp && i < sizeof(remotecwd) - 1; i++, cp++) { 83479971Sobrien if (cp[0] == '"') { 83579971Sobrien if (cp[1] == '"') 83679971Sobrien cp++; 83779971Sobrien else 83879971Sobrien break; 83979971Sobrien } 840142129Smikeh remotecwd[i] = *cp; 84179971Sobrien } 842142129Smikeh remotecwd[i] = '\0'; 843223328Sgavin DPRINTF("updateremotecwd: got `%s'\n", remotecwd); 844142129Smikeh goto cleanupremotecwd; 845142129Smikeh badremotecwd: 846142129Smikeh remotecwd[0]='\0'; 847142129Smikeh cleanupremotecwd: 84879971Sobrien verbose = overbose; 84979971Sobrien code = ocode; 85079971Sobrien} 85179971Sobrien 852142129Smikeh/* 853142129Smikeh * Ensure file is in or under dir. 854142129Smikeh * Returns 1 if so, 0 if not (or an error occurred). 855142129Smikeh */ 856142129Smikehint 857142129Smikehfileindir(const char *file, const char *dir) 858142129Smikeh{ 859223328Sgavin char parentdirbuf[PATH_MAX+1], *parentdir; 860223328Sgavin char realdir[PATH_MAX+1]; 861142129Smikeh size_t dirlen; 86279971Sobrien 863223328Sgavin /* determine parent directory of file */ 864223328Sgavin (void)strlcpy(parentdirbuf, file, sizeof(parentdirbuf)); 865223328Sgavin parentdir = dirname(parentdirbuf); 866223328Sgavin if (strcmp(parentdir, ".") == 0) 867223328Sgavin return 1; /* current directory is ok */ 868223328Sgavin 869223328Sgavin /* find the directory */ 870223328Sgavin if (realpath(parentdir, realdir) == NULL) { 871223328Sgavin warn("Unable to determine real path of `%s'", parentdir); 872142129Smikeh return 0; 873142129Smikeh } 874223328Sgavin if (realdir[0] != '/') /* relative result is ok */ 875142129Smikeh return 1; 876142129Smikeh dirlen = strlen(dir); 877223328Sgavin if (strncmp(realdir, dir, dirlen) == 0 && 878223328Sgavin (realdir[dirlen] == '/' || realdir[dirlen] == '\0')) 879142129Smikeh return 1; 880142129Smikeh return 0; 881142129Smikeh} 882142129Smikeh 88379971Sobrien/* 88479971Sobrien * List words in stringlist, vertically arranged 88579971Sobrien */ 88679971Sobrienvoid 88779971Sobrienlist_vertical(StringList *sl) 88879971Sobrien{ 889223328Sgavin size_t i, j; 890223328Sgavin size_t columns, lines; 89179971Sobrien char *p; 892223328Sgavin size_t w, width; 89379971Sobrien 89479971Sobrien width = 0; 89579971Sobrien 89679971Sobrien for (i = 0 ; i < sl->sl_cur ; i++) { 89779971Sobrien w = strlen(sl->sl_str[i]); 89879971Sobrien if (w > width) 89979971Sobrien width = w; 90079971Sobrien } 90179971Sobrien width = (width + 8) &~ 7; 90279971Sobrien 90379971Sobrien columns = ttywidth / width; 90479971Sobrien if (columns == 0) 90579971Sobrien columns = 1; 90679971Sobrien lines = (sl->sl_cur + columns - 1) / columns; 90779971Sobrien for (i = 0; i < lines; i++) { 90879971Sobrien for (j = 0; j < columns; j++) { 90979971Sobrien p = sl->sl_str[j * lines + i]; 91079971Sobrien if (p) 91179971Sobrien fputs(p, ttyout); 91279971Sobrien if (j * lines + i + lines >= sl->sl_cur) { 91379971Sobrien putc('\n', ttyout); 91479971Sobrien break; 91579971Sobrien } 916223328Sgavin if (p) { 917223328Sgavin w = strlen(p); 918223328Sgavin while (w < width) { 919223328Sgavin w = (w + 8) &~ 7; 920223328Sgavin (void)putc('\t', ttyout); 921223328Sgavin } 92279971Sobrien } 92379971Sobrien } 92479971Sobrien } 92579971Sobrien} 92679971Sobrien 92779971Sobrien/* 92879971Sobrien * Update the global ttywidth value, using TIOCGWINSZ. 92979971Sobrien */ 93079971Sobrienvoid 93179971Sobriensetttywidth(int a) 93279971Sobrien{ 93379971Sobrien struct winsize winsize; 93479971Sobrien int oerrno = errno; 93579971Sobrien 93679971Sobrien if (ioctl(fileno(ttyout), TIOCGWINSZ, &winsize) != -1 && 93779971Sobrien winsize.ws_col != 0) 93879971Sobrien ttywidth = winsize.ws_col; 93979971Sobrien else 94079971Sobrien ttywidth = 80; 94179971Sobrien errno = oerrno; 94279971Sobrien} 94379971Sobrien 94479971Sobrien/* 94579971Sobrien * Change the rate limit up (SIGUSR1) or down (SIGUSR2) 94679971Sobrien */ 94779971Sobrienvoid 94879971Sobriencrankrate(int sig) 94979971Sobrien{ 95079971Sobrien 95179971Sobrien switch (sig) { 95279971Sobrien case SIGUSR1: 95379971Sobrien if (rate_get) 95479971Sobrien rate_get += rate_get_incr; 95579971Sobrien if (rate_put) 95679971Sobrien rate_put += rate_put_incr; 95779971Sobrien break; 95879971Sobrien case SIGUSR2: 95979971Sobrien if (rate_get && rate_get > rate_get_incr) 96079971Sobrien rate_get -= rate_get_incr; 96179971Sobrien if (rate_put && rate_put > rate_put_incr) 96279971Sobrien rate_put -= rate_put_incr; 96379971Sobrien break; 96479971Sobrien default: 96579971Sobrien err(1, "crankrate invoked with unknown signal: %d", sig); 96679971Sobrien } 96779971Sobrien} 96879971Sobrien 96979971Sobrien 97079971Sobrien/* 97179971Sobrien * Setup or cleanup EditLine structures 97279971Sobrien */ 97379971Sobrien#ifndef NO_EDITCOMPLETE 97479971Sobrienvoid 97579971Sobriencontrolediting(void) 97679971Sobrien{ 97779971Sobrien if (editing && el == NULL && hist == NULL) { 97879971Sobrien HistEvent ev; 97979971Sobrien int editmode; 98079971Sobrien 98198247Smikeh el = el_init(getprogname(), stdin, ttyout, stderr); 98279971Sobrien /* init editline */ 98379971Sobrien hist = history_init(); /* init the builtin history */ 98479971Sobrien history(hist, &ev, H_SETSIZE, 100);/* remember 100 events */ 98579971Sobrien el_set(el, EL_HIST, history, hist); /* use history */ 98679971Sobrien 98779971Sobrien el_set(el, EL_EDITOR, "emacs"); /* default editor is emacs */ 98879971Sobrien el_set(el, EL_PROMPT, prompt); /* set the prompt functions */ 98979971Sobrien el_set(el, EL_RPROMPT, rprompt); 99079971Sobrien 99179971Sobrien /* add local file completion, bind to TAB */ 99279971Sobrien el_set(el, EL_ADDFN, "ftp-complete", 99379971Sobrien "Context sensitive argument completion", 99479971Sobrien complete); 99579971Sobrien el_set(el, EL_BIND, "^I", "ftp-complete", NULL); 99679971Sobrien el_source(el, NULL); /* read ~/.editrc */ 99779971Sobrien if ((el_get(el, EL_EDITMODE, &editmode) != -1) && editmode == 0) 99879971Sobrien editing = 0; /* the user doesn't want editing, 99979971Sobrien * so disable, and let statement 100079971Sobrien * below cleanup */ 100179971Sobrien else 100279971Sobrien el_set(el, EL_SIGNAL, 1); 100379971Sobrien } 100479971Sobrien if (!editing) { 100579971Sobrien if (hist) { 100679971Sobrien history_end(hist); 100779971Sobrien hist = NULL; 100879971Sobrien } 100979971Sobrien if (el) { 101079971Sobrien el_end(el); 101179971Sobrien el = NULL; 101279971Sobrien } 101379971Sobrien } 101479971Sobrien} 101579971Sobrien#endif /* !NO_EDITCOMPLETE */ 101679971Sobrien 101779971Sobrien/* 101879971Sobrien * Convert the string `arg' to an int, which may have an optional SI suffix 101979971Sobrien * (`b', `k', `m', `g'). Returns the number for success, -1 otherwise. 102079971Sobrien */ 102179971Sobrienint 102279971Sobrienstrsuftoi(const char *arg) 102379971Sobrien{ 102479971Sobrien char *cp; 102579971Sobrien long val; 102679971Sobrien 102779971Sobrien if (!isdigit((unsigned char)arg[0])) 102879971Sobrien return (-1); 102979971Sobrien 103079971Sobrien val = strtol(arg, &cp, 10); 103179971Sobrien if (cp != NULL) { 103279971Sobrien if (cp[0] != '\0' && cp[1] != '\0') 103379971Sobrien return (-1); 103479971Sobrien switch (tolower((unsigned char)cp[0])) { 103579971Sobrien case '\0': 103679971Sobrien case 'b': 103779971Sobrien break; 103879971Sobrien case 'k': 103979971Sobrien val <<= 10; 104079971Sobrien break; 104179971Sobrien case 'm': 104279971Sobrien val <<= 20; 104379971Sobrien break; 104479971Sobrien case 'g': 104579971Sobrien val <<= 30; 104679971Sobrien break; 104779971Sobrien default: 104879971Sobrien return (-1); 104979971Sobrien } 105079971Sobrien } 105179971Sobrien if (val < 0 || val > INT_MAX) 105279971Sobrien return (-1); 105379971Sobrien 105479971Sobrien return (val); 105579971Sobrien} 105679971Sobrien 105779971Sobrien/* 105879971Sobrien * Set up socket buffer sizes before a connection is made. 105979971Sobrien */ 106079971Sobrienvoid 106179971Sobriensetupsockbufsize(int sock) 106279971Sobrien{ 1063232779Sgavin socklen_t slen; 106479971Sobrien 1065232779Sgavin if (0 == rcvbuf_size) { 1066232779Sgavin slen = sizeof(rcvbuf_size); 1067232779Sgavin if (getsockopt(sock, SOL_SOCKET, SO_RCVBUF, 1068232779Sgavin (void *)&rcvbuf_size, &slen) == -1) 1069232779Sgavin err(1, "Unable to determine rcvbuf size"); 1070232779Sgavin if (rcvbuf_size <= 0) 1071232779Sgavin rcvbuf_size = 8 * 1024; 1072232779Sgavin if (rcvbuf_size > 8 * 1024 * 1024) 1073232779Sgavin rcvbuf_size = 8 * 1024 * 1024; 1074232779Sgavin DPRINTF("setupsockbufsize: rcvbuf_size determined as %d\n", 1075232779Sgavin rcvbuf_size); 1076232779Sgavin } 1077232779Sgavin if (0 == sndbuf_size) { 1078232779Sgavin slen = sizeof(sndbuf_size); 1079232779Sgavin if (getsockopt(sock, SOL_SOCKET, SO_SNDBUF, 1080232779Sgavin (void *)&sndbuf_size, &slen) == -1) 1081232779Sgavin err(1, "Unable to determine sndbuf size"); 1082232779Sgavin if (sndbuf_size <= 0) 1083232779Sgavin sndbuf_size = 8 * 1024; 1084232779Sgavin if (sndbuf_size > 8 * 1024 * 1024) 1085232779Sgavin sndbuf_size = 8 * 1024 * 1024; 1086232779Sgavin DPRINTF("setupsockbufsize: sndbuf_size determined as %d\n", 1087232779Sgavin sndbuf_size); 1088232779Sgavin } 1089232779Sgavin 1090146309Smikeh if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, 1091146309Smikeh (void *)&sndbuf_size, sizeof(sndbuf_size)) == -1) 1092223328Sgavin warn("Unable to set sndbuf size %d", sndbuf_size); 109379971Sobrien 1094146309Smikeh if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, 1095146309Smikeh (void *)&rcvbuf_size, sizeof(rcvbuf_size)) == -1) 1096223328Sgavin warn("Unable to set rcvbuf size %d", rcvbuf_size); 109779971Sobrien} 109879971Sobrien 109979971Sobrien/* 110079971Sobrien * Copy characters from src into dst, \ quoting characters that require it 110179971Sobrien */ 110279971Sobrienvoid 110379971Sobrienftpvis(char *dst, size_t dstlen, const char *src, size_t srclen) 110479971Sobrien{ 1105223328Sgavin size_t di, si; 110679971Sobrien 110779971Sobrien for (di = si = 0; 110879971Sobrien src[si] != '\0' && di < dstlen && si < srclen; 110979971Sobrien di++, si++) { 111079971Sobrien switch (src[si]) { 111179971Sobrien case '\\': 111279971Sobrien case ' ': 111379971Sobrien case '\t': 111479971Sobrien case '\r': 111579971Sobrien case '\n': 111679971Sobrien case '"': 111779971Sobrien dst[di++] = '\\'; 111879971Sobrien if (di >= dstlen) 111979971Sobrien break; 112079971Sobrien /* FALLTHROUGH */ 112179971Sobrien default: 112279971Sobrien dst[di] = src[si]; 112379971Sobrien } 112479971Sobrien } 112579971Sobrien dst[di] = '\0'; 112679971Sobrien} 112779971Sobrien 112879971Sobrien/* 112979971Sobrien * Copy src into buf (which is len bytes long), expanding % sequences. 113079971Sobrien */ 113179971Sobrienvoid 113279971Sobrienformatbuf(char *buf, size_t len, const char *src) 113379971Sobrien{ 1134223328Sgavin const char *p, *p2, *q; 1135223328Sgavin size_t i; 1136223328Sgavin int op, updirs, pdirs; 113779971Sobrien 113879971Sobrien#define ADDBUF(x) do { \ 113979971Sobrien if (i >= len - 1) \ 114079971Sobrien goto endbuf; \ 114179971Sobrien buf[i++] = (x); \ 114279971Sobrien } while (0) 114379971Sobrien 114479971Sobrien p = src; 114579971Sobrien for (i = 0; *p; p++) { 114679971Sobrien if (*p != '%') { 114779971Sobrien ADDBUF(*p); 114879971Sobrien continue; 114979971Sobrien } 115079971Sobrien p++; 115179971Sobrien 115279971Sobrien switch (op = *p) { 115379971Sobrien 115479971Sobrien case '/': 115579971Sobrien case '.': 115679971Sobrien case 'c': 1157142129Smikeh p2 = connected ? remotecwd : ""; 115879971Sobrien updirs = pdirs = 0; 115979971Sobrien 116079971Sobrien /* option to determine fixed # of dirs from path */ 116179971Sobrien if (op == '.' || op == 'c') { 116279971Sobrien int skip; 116379971Sobrien 116479971Sobrien q = p2; 116579971Sobrien while (*p2) /* calc # of /'s */ 116679971Sobrien if (*p2++ == '/') 116779971Sobrien updirs++; 116879971Sobrien if (p[1] == '0') { /* print <x> or ... */ 116979971Sobrien pdirs = 1; 117079971Sobrien p++; 117179971Sobrien } 117279971Sobrien if (p[1] >= '1' && p[1] <= '9') { 117379971Sobrien /* calc # to skip */ 117479971Sobrien skip = p[1] - '0'; 117579971Sobrien p++; 117679971Sobrien } else 117779971Sobrien skip = 1; 117879971Sobrien 117979971Sobrien updirs -= skip; 118079971Sobrien while (skip-- > 0) { 118179971Sobrien while ((p2 > q) && (*p2 != '/')) 118279971Sobrien p2--; /* back up */ 118379971Sobrien if (skip && p2 > q) 118479971Sobrien p2--; 118579971Sobrien } 118679971Sobrien if (*p2 == '/' && p2 != q) 118779971Sobrien p2++; 118879971Sobrien } 118979971Sobrien 119079971Sobrien if (updirs > 0 && pdirs) { 119179971Sobrien if (i >= len - 5) 119279971Sobrien break; 119379971Sobrien if (op == '.') { 119479971Sobrien ADDBUF('.'); 119579971Sobrien ADDBUF('.'); 119679971Sobrien ADDBUF('.'); 119779971Sobrien } else { 119879971Sobrien ADDBUF('/'); 119979971Sobrien ADDBUF('<'); 120079971Sobrien if (updirs > 9) { 120179971Sobrien ADDBUF('9'); 120279971Sobrien ADDBUF('+'); 120379971Sobrien } else 120479971Sobrien ADDBUF('0' + updirs); 120579971Sobrien ADDBUF('>'); 120679971Sobrien } 120779971Sobrien } 120879971Sobrien for (; *p2; p2++) 120979971Sobrien ADDBUF(*p2); 121079971Sobrien break; 121179971Sobrien 121279971Sobrien case 'M': 121379971Sobrien case 'm': 1214223328Sgavin for (p2 = connected && hostname ? hostname : "-"; 1215116424Smikeh *p2 ; p2++) { 121679971Sobrien if (op == 'm' && *p2 == '.') 121779971Sobrien break; 121879971Sobrien ADDBUF(*p2); 121979971Sobrien } 122079971Sobrien break; 122179971Sobrien 122279971Sobrien case 'n': 122379971Sobrien for (p2 = connected ? username : "-"; *p2 ; p2++) 122479971Sobrien ADDBUF(*p2); 122579971Sobrien break; 122679971Sobrien 122779971Sobrien case '%': 122879971Sobrien ADDBUF('%'); 122979971Sobrien break; 123079971Sobrien 123179971Sobrien default: /* display unknown codes literally */ 123279971Sobrien ADDBUF('%'); 123379971Sobrien ADDBUF(op); 123479971Sobrien break; 123579971Sobrien 123679971Sobrien } 123779971Sobrien } 123879971Sobrien endbuf: 123979971Sobrien buf[i] = '\0'; 124079971Sobrien} 124179971Sobrien 124279971Sobrien/* 124379971Sobrien * Determine if given string is an IPv6 address or not. 124479971Sobrien * Return 1 for yes, 0 for no 124579971Sobrien */ 124679971Sobrienint 124779971Sobrienisipv6addr(const char *addr) 124879971Sobrien{ 124979971Sobrien int rv = 0; 125079971Sobrien#ifdef INET6 125179971Sobrien struct addrinfo hints, *res; 125279971Sobrien 125379971Sobrien memset(&hints, 0, sizeof(hints)); 1254223328Sgavin hints.ai_family = AF_INET6; 125579971Sobrien hints.ai_socktype = SOCK_DGRAM; /*dummy*/ 125679971Sobrien hints.ai_flags = AI_NUMERICHOST; 125779971Sobrien if (getaddrinfo(addr, "0", &hints, &res) != 0) 125879971Sobrien rv = 0; 125979971Sobrien else { 126079971Sobrien rv = 1; 126179971Sobrien freeaddrinfo(res); 126279971Sobrien } 1263223328Sgavin DPRINTF("isipv6addr: got %d for %s\n", rv, addr); 126479971Sobrien#endif 126579971Sobrien return (rv == 1) ? 1 : 0; 126679971Sobrien} 126779971Sobrien 1268223328Sgavin/* 1269223328Sgavin * Read a line from the FILE stream into buf/buflen using fgets(), so up 1270223328Sgavin * to buflen-1 chars will be read and the result will be NUL terminated. 1271223328Sgavin * If the line has a trailing newline it will be removed. 1272223328Sgavin * If the line is too long, excess characters will be read until 1273223328Sgavin * newline/EOF/error. 1274223328Sgavin * If EOF/error occurs or a too-long line is encountered and errormsg 1275223328Sgavin * isn't NULL, it will be changed to a description of the problem. 1276223328Sgavin * (The EOF message has a leading \n for cosmetic purposes). 1277223328Sgavin * Returns: 1278223328Sgavin * >=0 length of line (excluding trailing newline) if all ok 1279223328Sgavin * -1 error occurred 1280223328Sgavin * -2 EOF encountered 1281223328Sgavin * -3 line was too long 1282223328Sgavin */ 1283223328Sgavinint 1284223328Sgavinget_line(FILE *stream, char *buf, size_t buflen, const char **errormsg) 1285223328Sgavin{ 1286223328Sgavin int rv, ch; 1287223328Sgavin size_t len; 128879971Sobrien 1289223328Sgavin if (fgets(buf, buflen, stream) == NULL) { 1290223328Sgavin if (feof(stream)) { /* EOF */ 1291223328Sgavin rv = -2; 1292223328Sgavin if (errormsg) 1293223328Sgavin *errormsg = "\nEOF received"; 1294223328Sgavin } else { /* error */ 1295223328Sgavin rv = -1; 1296223328Sgavin if (errormsg) 1297223328Sgavin *errormsg = "Error encountered"; 1298223328Sgavin } 1299223328Sgavin clearerr(stream); 1300223328Sgavin return rv; 1301223328Sgavin } 1302223328Sgavin len = strlen(buf); 1303223328Sgavin if (buf[len-1] == '\n') { /* clear any trailing newline */ 1304223328Sgavin buf[--len] = '\0'; 1305223328Sgavin } else if (len == buflen-1) { /* line too long */ 1306223328Sgavin while ((ch = getchar()) != '\n' && ch != EOF) 1307223328Sgavin continue; 1308223328Sgavin if (errormsg) 1309223328Sgavin *errormsg = "Input line is too long"; 1310223328Sgavin clearerr(stream); 1311223328Sgavin return -3; 1312223328Sgavin } 1313223328Sgavin if (errormsg) 1314223328Sgavin *errormsg = NULL; 1315223328Sgavin return len; 1316223328Sgavin} 1317223328Sgavin 131879971Sobrien/* 1319223328Sgavin * Internal version of connect(2); sets socket buffer sizes, 1320223328Sgavin * binds to a specific local address (if set), and 1321146309Smikeh * supports a connection timeout using a non-blocking connect(2) with 1322146309Smikeh * a poll(2). 1323146309Smikeh * Socket fcntl flags are temporarily updated to include O_NONBLOCK; 1324146309Smikeh * these will not be reverted on connection failure. 1325223328Sgavin * Returns 0 on success, or -1 upon failure (with an appropriate 1326223328Sgavin * error message displayed.) 132779971Sobrien */ 132879971Sobrienint 1329223328Sgavinftp_connect(int sock, const struct sockaddr *name, socklen_t namelen) 133079971Sobrien{ 1331146309Smikeh int flags, rv, timeout, error; 1332146309Smikeh socklen_t slen; 1333146309Smikeh struct timeval endtime, now, td; 1334146309Smikeh struct pollfd pfd[1]; 1335223328Sgavin char hname[NI_MAXHOST]; 1336223328Sgavin char sname[NI_MAXSERV]; 133779971Sobrien 133879971Sobrien setupsockbufsize(sock); 1339223328Sgavin if (getnameinfo(name, namelen, 1340223328Sgavin hname, sizeof(hname), sname, sizeof(sname), 1341223328Sgavin NI_NUMERICHOST | NI_NUMERICSERV) != 0) { 1342223328Sgavin strlcpy(hname, "?", sizeof(hname)); 1343223328Sgavin strlcpy(sname, "?", sizeof(sname)); 1344223328Sgavin } 1345128671Smikeh 1346223328Sgavin if (bindai != NULL) { /* bind to specific addr */ 1347223328Sgavin struct addrinfo *ai; 1348146309Smikeh 1349223328Sgavin for (ai = bindai; ai != NULL; ai = ai->ai_next) { 1350223328Sgavin if (ai->ai_family == name->sa_family) 1351223328Sgavin break; 1352223328Sgavin } 1353223328Sgavin if (ai == NULL) 1354223328Sgavin ai = bindai; 1355223328Sgavin if (bind(sock, ai->ai_addr, ai->ai_addrlen) == -1) { 1356223328Sgavin char bname[NI_MAXHOST]; 1357223328Sgavin int saveerr; 1358223328Sgavin 1359223328Sgavin saveerr = errno; 1360223328Sgavin if (getnameinfo(ai->ai_addr, ai->ai_addrlen, 1361223328Sgavin bname, sizeof(bname), NULL, 0, NI_NUMERICHOST) != 0) 1362223328Sgavin strlcpy(bname, "?", sizeof(bname)); 1363223328Sgavin errno = saveerr; 1364223328Sgavin warn("Can't bind to `%s'", bname); 1365223328Sgavin return -1; 1366223328Sgavin } 1367223328Sgavin } 1368223328Sgavin 1369223328Sgavin /* save current socket flags */ 1370223328Sgavin if ((flags = fcntl(sock, F_GETFL, 0)) == -1) { 1371223328Sgavin warn("Can't %s socket flags for connect to `%s:%s'", 1372223328Sgavin "save", hname, sname); 1373223328Sgavin return -1; 1374223328Sgavin } 1375223328Sgavin /* set non-blocking connect */ 1376223328Sgavin if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) { 1377223328Sgavin warn("Can't set socket non-blocking for connect to `%s:%s'", 1378223328Sgavin hname, sname); 1379223328Sgavin return -1; 1380223328Sgavin } 1381223328Sgavin 1382146309Smikeh /* NOTE: we now must restore socket flags on successful exit */ 1383146309Smikeh 1384146309Smikeh pfd[0].fd = sock; 1385146309Smikeh pfd[0].events = POLLIN|POLLOUT; 1386146309Smikeh 1387146309Smikeh if (quit_time > 0) { /* want a non default timeout */ 1388146309Smikeh (void)gettimeofday(&endtime, NULL); 1389146309Smikeh endtime.tv_sec += quit_time; /* determine end time */ 1390146309Smikeh } 1391146309Smikeh 1392146309Smikeh rv = connect(sock, name, namelen); /* inititate the connection */ 1393146309Smikeh if (rv == -1) { /* connection error */ 1394223328Sgavin if (errno != EINPROGRESS) { /* error isn't "please wait" */ 1395223328Sgavin connecterror: 1396223328Sgavin warn("Can't connect to `%s:%s'", hname, sname); 1397146309Smikeh return -1; 1398223328Sgavin } 1399146309Smikeh 1400146309Smikeh /* connect EINPROGRESS; wait */ 1401128671Smikeh do { 1402146309Smikeh if (quit_time > 0) { /* determine timeout */ 1403146309Smikeh (void)gettimeofday(&now, NULL); 1404146309Smikeh timersub(&endtime, &now, &td); 1405146309Smikeh timeout = td.tv_sec * 1000 + td.tv_usec/1000; 1406146309Smikeh if (timeout < 0) 1407146309Smikeh timeout = 0; 1408146309Smikeh } else { 1409146309Smikeh timeout = INFTIM; 1410146309Smikeh } 1411146309Smikeh pfd[0].revents = 0; 1412223328Sgavin rv = ftp_poll(pfd, 1, timeout); 1413146309Smikeh /* loop until poll ! EINTR */ 1414128671Smikeh } while (rv == -1 && errno == EINTR); 1415146309Smikeh 1416146309Smikeh if (rv == 0) { /* poll (connect) timed out */ 1417146309Smikeh errno = ETIMEDOUT; 1418223328Sgavin goto connecterror; 1419146309Smikeh } 1420146309Smikeh 1421146309Smikeh if (rv == -1) { /* poll error */ 1422223328Sgavin goto connecterror; 1423146309Smikeh } else if (pfd[0].revents & (POLLIN|POLLOUT)) { 1424146309Smikeh slen = sizeof(error); /* OK, or pending error */ 1425146309Smikeh if (getsockopt(sock, SOL_SOCKET, SO_ERROR, 1426223328Sgavin &error, &slen) == -1) { 1427223328Sgavin /* Solaris pending error */ 1428223328Sgavin goto connecterror; 1429223328Sgavin } else if (error != 0) { 1430146309Smikeh errno = error; /* BSD pending error */ 1431223328Sgavin goto connecterror; 1432146309Smikeh } 1433146309Smikeh } else { 1434146309Smikeh errno = EBADF; /* this shouldn't happen ... */ 1435223328Sgavin goto connecterror; 1436146309Smikeh } 1437128671Smikeh } 1438146309Smikeh 1439223328Sgavin if (fcntl(sock, F_SETFL, flags) == -1) { 1440223328Sgavin /* restore socket flags */ 1441223328Sgavin warn("Can't %s socket flags for connect to `%s:%s'", 1442223328Sgavin "restore", hname, sname); 1443146309Smikeh return -1; 1444223328Sgavin } 1445146309Smikeh return 0; 144679971Sobrien} 144779971Sobrien 144879971Sobrien/* 144979971Sobrien * Internal version of listen(2); sets socket buffer sizes first. 145079971Sobrien */ 145179971Sobrienint 1452223328Sgavinftp_listen(int sock, int backlog) 145379971Sobrien{ 145479971Sobrien 145579971Sobrien setupsockbufsize(sock); 145679971Sobrien return (listen(sock, backlog)); 145779971Sobrien} 145879971Sobrien 145979971Sobrien/* 1460146309Smikeh * Internal version of poll(2), to allow reimplementation by select(2) 1461146309Smikeh * on platforms without the former. 1462146309Smikeh */ 1463146309Smikehint 1464223328Sgavinftp_poll(struct pollfd *fds, int nfds, int timeout) 1465146309Smikeh{ 1466223328Sgavin#if defined(HAVE_POLL) 1467146309Smikeh return poll(fds, nfds, timeout); 1468223328Sgavin 1469223328Sgavin#elif defined(HAVE_SELECT) 1470223328Sgavin /* implement poll(2) using select(2) */ 1471223328Sgavin fd_set rset, wset, xset; 1472223328Sgavin const int rsetflags = POLLIN | POLLRDNORM; 1473223328Sgavin const int wsetflags = POLLOUT | POLLWRNORM; 1474223328Sgavin const int xsetflags = POLLRDBAND; 1475223328Sgavin struct timeval tv, *ptv; 1476223328Sgavin int i, max, rv; 1477223328Sgavin 1478223328Sgavin FD_ZERO(&rset); /* build list of read & write events */ 1479223328Sgavin FD_ZERO(&wset); 1480223328Sgavin FD_ZERO(&xset); 1481223328Sgavin max = 0; 1482223328Sgavin for (i = 0; i < nfds; i++) { 1483223328Sgavin if (fds[i].fd > FD_SETSIZE) { 1484223328Sgavin warnx("can't select fd %d", fds[i].fd); 1485223328Sgavin errno = EINVAL; 1486223328Sgavin return -1; 1487223328Sgavin } else if (fds[i].fd > max) 1488223328Sgavin max = fds[i].fd; 1489223328Sgavin if (fds[i].events & rsetflags) 1490223328Sgavin FD_SET(fds[i].fd, &rset); 1491223328Sgavin if (fds[i].events & wsetflags) 1492223328Sgavin FD_SET(fds[i].fd, &wset); 1493223328Sgavin if (fds[i].events & xsetflags) 1494223328Sgavin FD_SET(fds[i].fd, &xset); 1495223328Sgavin } 1496223328Sgavin 1497223328Sgavin ptv = &tv; /* determine timeout */ 1498223328Sgavin if (timeout == -1) { /* wait forever */ 1499223328Sgavin ptv = NULL; 1500223328Sgavin } else if (timeout == 0) { /* poll once */ 1501223328Sgavin ptv->tv_sec = 0; 1502223328Sgavin ptv->tv_usec = 0; 1503223328Sgavin } 1504223328Sgavin else if (timeout != 0) { /* wait timeout milliseconds */ 1505223328Sgavin ptv->tv_sec = timeout / 1000; 1506223328Sgavin ptv->tv_usec = (timeout % 1000) * 1000; 1507223328Sgavin } 1508223328Sgavin rv = select(max + 1, &rset, &wset, &xset, ptv); 1509223328Sgavin if (rv <= 0) /* -1 == error, 0 == timeout */ 1510223328Sgavin return rv; 1511223328Sgavin 1512223328Sgavin for (i = 0; i < nfds; i++) { /* determine results */ 1513223328Sgavin if (FD_ISSET(fds[i].fd, &rset)) 1514223328Sgavin fds[i].revents |= (fds[i].events & rsetflags); 1515223328Sgavin if (FD_ISSET(fds[i].fd, &wset)) 1516223328Sgavin fds[i].revents |= (fds[i].events & wsetflags); 1517223328Sgavin if (FD_ISSET(fds[i].fd, &xset)) 1518223328Sgavin fds[i].revents |= (fds[i].events & xsetflags); 1519223328Sgavin } 1520223328Sgavin return rv; 1521223328Sgavin 1522223328Sgavin#else 1523223328Sgavin# error no way to implement xpoll 1524223328Sgavin#endif 1525146309Smikeh} 1526146309Smikeh 1527146309Smikeh/* 152879971Sobrien * malloc() with inbuilt error checking 152979971Sobrien */ 153079971Sobrienvoid * 1531223328Sgavinftp_malloc(size_t size) 153279971Sobrien{ 153379971Sobrien void *p; 153479971Sobrien 153579971Sobrien p = malloc(size); 153679971Sobrien if (p == NULL) 153779971Sobrien err(1, "Unable to allocate %ld bytes of memory", (long)size); 153879971Sobrien return (p); 153979971Sobrien} 154079971Sobrien 154179971Sobrien/* 154279971Sobrien * sl_init() with inbuilt error checking 154379971Sobrien */ 154479971SobrienStringList * 1545223328Sgavinftp_sl_init(void) 154679971Sobrien{ 154779971Sobrien StringList *p; 154879971Sobrien 154979971Sobrien p = sl_init(); 155079971Sobrien if (p == NULL) 155179971Sobrien err(1, "Unable to allocate memory for stringlist"); 155279971Sobrien return (p); 155379971Sobrien} 155479971Sobrien 155579971Sobrien/* 155679971Sobrien * sl_add() with inbuilt error checking 155779971Sobrien */ 155879971Sobrienvoid 1559223328Sgavinftp_sl_add(StringList *sl, char *i) 156079971Sobrien{ 156179971Sobrien 156279971Sobrien if (sl_add(sl, i) == -1) 156379971Sobrien err(1, "Unable to add `%s' to stringlist", i); 156479971Sobrien} 156579971Sobrien 156679971Sobrien/* 156779971Sobrien * strdup() with inbuilt error checking 156879971Sobrien */ 156979971Sobrienchar * 1570223328Sgavinftp_strdup(const char *str) 157179971Sobrien{ 157279971Sobrien char *s; 157379971Sobrien 157479971Sobrien if (str == NULL) 1575223328Sgavin errx(1, "ftp_strdup: called with NULL argument"); 157679971Sobrien s = strdup(str); 157779971Sobrien if (s == NULL) 157879971Sobrien err(1, "Unable to allocate memory for string copy"); 157979971Sobrien return (s); 158079971Sobrien} 1581