ftp.c revision 93150
137535Sdes/*- 237535Sdes * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav 337535Sdes * All rights reserved. 437535Sdes * 537535Sdes * Redistribution and use in source and binary forms, with or without 637535Sdes * modification, are permitted provided that the following conditions 737535Sdes * are met: 837535Sdes * 1. Redistributions of source code must retain the above copyright 937535Sdes * notice, this list of conditions and the following disclaimer 1037535Sdes * in this position and unchanged. 1137535Sdes * 2. Redistributions in binary form must reproduce the above copyright 1237535Sdes * notice, this list of conditions and the following disclaimer in the 1337535Sdes * documentation and/or other materials provided with the distribution. 1437535Sdes * 3. The name of the author may not be used to endorse or promote products 1537535Sdes * derived from this software without specific prior written permission 1637535Sdes * 1737535Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 1837535Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 1937535Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 2037535Sdes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 2137535Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 2237535Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 2337535Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 2437535Sdes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2537535Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 2637535Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2737535Sdes */ 2837535Sdes 2984203Sdillon#include <sys/cdefs.h> 3084203Sdillon__FBSDID("$FreeBSD: head/lib/libfetch/ftp.c 93150 2002-03-25 13:53:46Z phk $"); 3184203Sdillon 3237535Sdes/* 3337571Sdes * Portions of this code were taken from or based on ftpio.c: 3437535Sdes * 3537535Sdes * ---------------------------------------------------------------------------- 3637535Sdes * "THE BEER-WARE LICENSE" (Revision 42): 3793150Sphk * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you 3837535Sdes * can do whatever you want with this stuff. If we meet some day, and you think 3937535Sdes * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 4037535Sdes * ---------------------------------------------------------------------------- 4137535Sdes * 4237535Sdes * Major Changelog: 4337535Sdes * 4437535Sdes * Dag-Erling Co�dan Sm�rgrav 4537535Sdes * 9 Jun 1998 4637535Sdes * 4737535Sdes * Incorporated into libfetch 4837535Sdes * 4937535Sdes * Jordan K. Hubbard 5037535Sdes * 17 Jan 1996 5137535Sdes * 5237535Sdes * Turned inside out. Now returns xfers as new file ids, not as a special 5337535Sdes * `state' of FTP_t 5437535Sdes * 5537535Sdes * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $ 5637535Sdes * 5737535Sdes */ 5837535Sdes 5941862Sdes#include <sys/param.h> 6037535Sdes#include <sys/socket.h> 6137535Sdes#include <netinet/in.h> 6237535Sdes 6337535Sdes#include <ctype.h> 6475891Sarchie#include <err.h> 6555557Sdes#include <errno.h> 6667430Sdes#include <fcntl.h> 6760188Sdes#include <netdb.h> 6837573Sdes#include <stdarg.h> 6937535Sdes#include <stdio.h> 7037571Sdes#include <stdlib.h> 7137535Sdes#include <string.h> 7241869Sdes#include <time.h> 7337571Sdes#include <unistd.h> 7437535Sdes 7537535Sdes#include "fetch.h" 7640939Sdes#include "common.h" 7741862Sdes#include "ftperr.h" 7837535Sdes 7970795Sdes#define FTP_ANONYMOUS_USER "anonymous" 8037535Sdes 8164883Sdes#define FTP_CONNECTION_ALREADY_OPEN 125 8237573Sdes#define FTP_OPEN_DATA_CONNECTION 150 8337573Sdes#define FTP_OK 200 8441869Sdes#define FTP_FILE_STATUS 213 8541863Sdes#define FTP_SERVICE_READY 220 8667890Sdes#define FTP_TRANSFER_COMPLETE 226 8737573Sdes#define FTP_PASSIVE_MODE 227 8860737Sume#define FTP_LPASSIVE_MODE 228 8960737Sume#define FTP_EPASSIVE_MODE 229 9037573Sdes#define FTP_LOGGED_IN 230 9137573Sdes#define FTP_FILE_ACTION_OK 250 9237573Sdes#define FTP_NEED_PASSWORD 331 9337573Sdes#define FTP_NEED_ACCOUNT 332 9460188Sdes#define FTP_FILE_OK 350 9555557Sdes#define FTP_SYNTAX_ERROR 500 9663336Sdes#define FTP_PROTOCOL_ERROR 999 9737573Sdes 9840975Sdesstatic struct url cached_host; 9990267Sdesstatic int cached_socket; 10037535Sdes 10190267Sdesstatic char *last_reply; 10290267Sdesstatic size_t lr_size; 10390267Sdesstatic size_t lr_length; 10490267Sdesstatic int last_code; 10537571Sdes 10655557Sdes#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 10760707Sdes && isdigit(foo[2]) \ 10890267Sdes && (foo[3] == ' ' || foo[3] == '\0')) 10955557Sdes#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 11055557Sdes && isdigit(foo[2]) && foo[3] == '-') 11155557Sdes 11290267Sdes/* 11390267Sdes * Translate IPv4 mapped IPv6 address to IPv4 address 11490267Sdes */ 11560737Sumestatic void 11660737Sumeunmappedaddr(struct sockaddr_in6 *sin6) 11760737Sume{ 11890267Sdes struct sockaddr_in *sin4; 11990267Sdes u_int32_t addr; 12090267Sdes int port; 12160737Sume 12290267Sdes if (sin6->sin6_family != AF_INET6 || 12390267Sdes !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 12490267Sdes return; 12590267Sdes sin4 = (struct sockaddr_in *)sin6; 12690267Sdes addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 12790267Sdes port = sin6->sin6_port; 12890267Sdes memset(sin4, 0, sizeof(struct sockaddr_in)); 12990267Sdes sin4->sin_addr.s_addr = addr; 13090267Sdes sin4->sin_port = port; 13190267Sdes sin4->sin_family = AF_INET; 13290267Sdes sin4->sin_len = sizeof(struct sockaddr_in); 13360737Sume} 13460737Sume 13537571Sdes/* 13655557Sdes * Get server response 13737535Sdes */ 13837535Sdesstatic int 13955557Sdes_ftp_chkerr(int cd) 14037535Sdes{ 14190267Sdes if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 14262215Sdes _fetch_syserr(); 14390267Sdes return (-1); 14462215Sdes } 14590267Sdes if (isftpinfo(last_reply)) { 14690267Sdes while (lr_length && !isftpreply(last_reply)) { 14790267Sdes if (_fetch_getln(cd, &last_reply, 14890267Sdes &lr_size, &lr_length) == -1) { 14990267Sdes _fetch_syserr(); 15090267Sdes return (-1); 15190267Sdes } 15290267Sdes } 15390267Sdes } 15455557Sdes 15590267Sdes while (lr_length && isspace(last_reply[lr_length-1])) 15690267Sdes lr_length--; 15790267Sdes last_reply[lr_length] = 0; 15837535Sdes 15990267Sdes if (!isftpreply(last_reply)) { 16090267Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 16190267Sdes return (-1); 16290267Sdes } 16355557Sdes 16490267Sdes last_code = (last_reply[0] - '0') * 100 16590267Sdes + (last_reply[1] - '0') * 10 16690267Sdes + (last_reply[2] - '0'); 16790267Sdes 16890267Sdes return (last_code); 16937535Sdes} 17037535Sdes 17137535Sdes/* 17237573Sdes * Send a command and check reply 17337535Sdes */ 17437535Sdesstatic int 17575891Sarchie_ftp_cmd(int cd, const char *fmt, ...) 17637535Sdes{ 17790267Sdes va_list ap; 17890267Sdes size_t len; 17990267Sdes char *msg; 18090267Sdes int r; 18137573Sdes 18290267Sdes va_start(ap, fmt); 18390267Sdes len = vasprintf(&msg, fmt, ap); 18490267Sdes va_end(ap); 18590267Sdes 18690267Sdes if (msg == NULL) { 18790267Sdes errno = ENOMEM; 18890267Sdes _fetch_syserr(); 18990267Sdes return (-1); 19090267Sdes } 19190267Sdes 19290267Sdes r = _fetch_putln(cd, msg, len); 19390267Sdes free(msg); 19490267Sdes 19590267Sdes if (r == -1) { 19690267Sdes _fetch_syserr(); 19790267Sdes return (-1); 19890267Sdes } 19990267Sdes 20090267Sdes return (_ftp_chkerr(cd)); 20137535Sdes} 20237535Sdes 20337535Sdes/* 20463340Sdes * Return a pointer to the filename part of a path 20563340Sdes */ 20675891Sarchiestatic const char * 20775891Sarchie_ftp_filename(const char *file) 20863340Sdes{ 20990267Sdes char *s; 21090267Sdes 21190267Sdes if ((s = strrchr(file, '/')) == NULL) 21290267Sdes return (file); 21390267Sdes else 21490267Sdes return (s + 1); 21563340Sdes} 21663340Sdes 21763340Sdes/* 21890267Sdes * Change working directory to the directory that contains the specified 21990267Sdes * file. 22063340Sdes */ 22163340Sdesstatic int 22275891Sarchie_ftp_cwd(int cd, const char *file) 22363340Sdes{ 22490267Sdes char *s; 22590267Sdes int e; 22663340Sdes 22790267Sdes if ((s = strrchr(file, '/')) == NULL || s == file) { 22890267Sdes e = _ftp_cmd(cd, "CWD /"); 22990267Sdes } else { 23090267Sdes e = _ftp_cmd(cd, "CWD %.*s", s - file, file); 23190267Sdes } 23290267Sdes if (e != FTP_FILE_ACTION_OK) { 23390267Sdes _ftp_seterr(e); 23490267Sdes return (-1); 23590267Sdes } 23690267Sdes return (0); 23763340Sdes} 23863340Sdes 23963340Sdes/* 24063340Sdes * Request and parse file stats 24163340Sdes */ 24263340Sdesstatic int 24375891Sarchie_ftp_stat(int cd, const char *file, struct url_stat *us) 24463340Sdes{ 24590267Sdes char *ln; 24690267Sdes const char *s; 24790267Sdes struct tm tm; 24890267Sdes time_t t; 24990267Sdes int e; 25063340Sdes 25175292Sdes us->size = -1; 25290267Sdes us->atime = us->mtime = 0; 25363340Sdes 25490267Sdes if ((s = strrchr(file, '/')) == NULL) 25590267Sdes s = file; 25690267Sdes else 25790267Sdes ++s; 25890267Sdes 25990267Sdes if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) { 26090267Sdes _ftp_seterr(e); 26190267Sdes return (-1); 26290267Sdes } 26390267Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 26490267Sdes /* nothing */ ; 26590267Sdes for (us->size = 0; *ln && isdigit(*ln); ln++) 26690267Sdes us->size = us->size * 10 + *ln - '0'; 26790267Sdes if (*ln && !isspace(*ln)) { 26890267Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 26990267Sdes us->size = -1; 27090267Sdes return (-1); 27190267Sdes } 27290267Sdes if (us->size == 0) 27390267Sdes us->size = -1; 27490267Sdes DEBUG(fprintf(stderr, "size: [%lld]\n", (long long)us->size)); 27590267Sdes 27690267Sdes if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) { 27790267Sdes _ftp_seterr(e); 27890267Sdes return (-1); 27990267Sdes } 28090267Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 28190267Sdes /* nothing */ ; 28290267Sdes switch (strspn(ln, "0123456789")) { 28390267Sdes case 14: 28490267Sdes break; 28590267Sdes case 15: 28690267Sdes ln++; 28790267Sdes ln[0] = '2'; 28890267Sdes ln[1] = '0'; 28990267Sdes break; 29090267Sdes default: 29190267Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 29290267Sdes return (-1); 29390267Sdes } 29490267Sdes if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 29590267Sdes &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 29690267Sdes &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { 29790267Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 29890267Sdes return (-1); 29990267Sdes } 30090267Sdes tm.tm_mon--; 30190267Sdes tm.tm_year -= 1900; 30290267Sdes tm.tm_isdst = -1; 30390267Sdes t = timegm(&tm); 30490267Sdes if (t == (time_t)-1) 30590267Sdes t = time(NULL); 30690267Sdes us->mtime = t; 30790267Sdes us->atime = t; 30890267Sdes DEBUG(fprintf(stderr, 30990267Sdes "last modified: [%04d-%02d-%02d %02d:%02d:%02d]\n", 31090267Sdes tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 31190267Sdes tm.tm_hour, tm.tm_min, tm.tm_sec)); 31290267Sdes return (0); 31363340Sdes} 31463340Sdes 31563340Sdes/* 31667430Sdes * I/O functions for FTP 31767430Sdes */ 31867430Sdesstruct ftpio { 31990267Sdes int csd; /* Control socket descriptor */ 32090267Sdes int dsd; /* Data socket descriptor */ 32190267Sdes int dir; /* Direction */ 32290267Sdes int eof; /* EOF reached */ 32390267Sdes int err; /* Error code */ 32467430Sdes}; 32567430Sdes 32690267Sdesstatic int _ftp_readfn(void *, char *, int); 32790267Sdesstatic int _ftp_writefn(void *, const char *, int); 32890267Sdesstatic fpos_t _ftp_seekfn(void *, fpos_t, int); 32990267Sdesstatic int _ftp_closefn(void *); 33067430Sdes 33167430Sdesstatic int 33267430Sdes_ftp_readfn(void *v, char *buf, int len) 33367430Sdes{ 33490267Sdes struct ftpio *io; 33590267Sdes int r; 33667430Sdes 33790267Sdes io = (struct ftpio *)v; 33890267Sdes if (io == NULL) { 33990267Sdes errno = EBADF; 34090267Sdes return (-1); 34190267Sdes } 34290267Sdes if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) { 34390267Sdes errno = EBADF; 34490267Sdes return (-1); 34590267Sdes } 34690267Sdes if (io->err) { 34790267Sdes errno = io->err; 34890267Sdes return (-1); 34990267Sdes } 35090267Sdes if (io->eof) 35190267Sdes return (0); 35290267Sdes r = read(io->dsd, buf, len); 35390267Sdes if (r > 0) 35490267Sdes return (r); 35590267Sdes if (r == 0) { 35690267Sdes io->eof = 1; 35790267Sdes return (0); 35890267Sdes } 35990267Sdes if (errno != EINTR) 36090267Sdes io->err = errno; 36190267Sdes return (-1); 36267430Sdes} 36367430Sdes 36467430Sdesstatic int 36567430Sdes_ftp_writefn(void *v, const char *buf, int len) 36667430Sdes{ 36790267Sdes struct ftpio *io; 36890267Sdes int w; 36990267Sdes 37090267Sdes io = (struct ftpio *)v; 37190267Sdes if (io == NULL) { 37290267Sdes errno = EBADF; 37390267Sdes return (-1); 37490267Sdes } 37590267Sdes if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) { 37690267Sdes errno = EBADF; 37790267Sdes return (-1); 37890267Sdes } 37990267Sdes if (io->err) { 38090267Sdes errno = io->err; 38190267Sdes return (-1); 38290267Sdes } 38390267Sdes w = write(io->dsd, buf, len); 38490267Sdes if (w >= 0) 38590267Sdes return (w); 38690267Sdes if (errno != EINTR) 38790267Sdes io->err = errno; 38890267Sdes return (-1); 38967430Sdes} 39067430Sdes 39167430Sdesstatic fpos_t 39285093Sdes_ftp_seekfn(void *v, fpos_t pos __unused, int whence __unused) 39367430Sdes{ 39490267Sdes struct ftpio *io; 39590267Sdes 39690267Sdes io = (struct ftpio *)v; 39790267Sdes if (io == NULL) { 39890267Sdes errno = EBADF; 39990267Sdes return (-1); 40090267Sdes } 40190267Sdes errno = ESPIPE; 40290267Sdes return (-1); 40367430Sdes} 40467430Sdes 40567430Sdesstatic int 40667430Sdes_ftp_closefn(void *v) 40767430Sdes{ 40890267Sdes struct ftpio *io; 40990267Sdes int r; 41067430Sdes 41190267Sdes io = (struct ftpio *)v; 41290267Sdes if (io == NULL) { 41390267Sdes errno = EBADF; 41490267Sdes return (-1); 41590267Sdes } 41690267Sdes if (io->dir == -1) 41790267Sdes return (0); 41890267Sdes if (io->csd == -1 || io->dsd == -1) { 41990267Sdes errno = EBADF; 42090267Sdes return (-1); 42190267Sdes } 42290267Sdes close(io->dsd); 42390267Sdes io->dir = -1; 42490267Sdes io->dsd = -1; 42590267Sdes DEBUG(fprintf(stderr, "Waiting for final status\n")); 42690267Sdes r = _ftp_chkerr(io->csd); 42790267Sdes close(io->csd); 42890267Sdes free(io); 42990267Sdes return (r == FTP_TRANSFER_COMPLETE) ? 0 : -1; 43067430Sdes} 43167430Sdes 43267430Sdesstatic FILE * 43367430Sdes_ftp_setup(int csd, int dsd, int mode) 43467430Sdes{ 43590267Sdes struct ftpio *io; 43690267Sdes FILE *f; 43767430Sdes 43890267Sdes if ((io = malloc(sizeof *io)) == NULL) 43990267Sdes return (NULL); 44090267Sdes io->csd = dup(csd); 44190267Sdes io->dsd = dsd; 44290267Sdes io->dir = mode; 44390267Sdes io->eof = io->err = 0; 44490267Sdes f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn); 44590267Sdes if (f == NULL) 44690267Sdes free(io); 44790267Sdes return (f); 44867430Sdes} 44967430Sdes 45067430Sdes/* 45137608Sdes * Transfer file 45237535Sdes */ 45337535Sdesstatic FILE * 45475891Sarchie_ftp_transfer(int cd, const char *oper, const char *file, 45590267Sdes int mode, off_t offset, const char *flags) 45637535Sdes{ 45790267Sdes struct sockaddr_storage sa; 45890267Sdes struct sockaddr_in6 *sin6; 45990267Sdes struct sockaddr_in *sin4; 46090267Sdes int low, pasv, verbose; 46190267Sdes int e, sd = -1; 46290267Sdes socklen_t l; 46390267Sdes char *s; 46490267Sdes FILE *df; 46555544Sdes 46690267Sdes /* check flags */ 46790267Sdes low = CHECK_FLAG('l'); 46890267Sdes pasv = CHECK_FLAG('p'); 46990267Sdes verbose = CHECK_FLAG('v'); 47055544Sdes 47190267Sdes /* passive mode */ 47290267Sdes if (!pasv) 47390267Sdes pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL && 47490267Sdes strncasecmp(s, "no", 2) != 0); 47560951Sdes 47690267Sdes /* find our own address, bind, and listen */ 47790267Sdes l = sizeof sa; 47890267Sdes if (getsockname(cd, (struct sockaddr *)&sa, &l) == -1) 47990267Sdes goto sysouch; 48090267Sdes if (sa.ss_family == AF_INET6) 48190267Sdes unmappedaddr((struct sockaddr_in6 *)&sa); 48260737Sume 48390267Sdes /* open data socket */ 48490267Sdes if ((sd = socket(sa.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 48590267Sdes _fetch_syserr(); 48690267Sdes return (NULL); 48760737Sume } 48837573Sdes 48990267Sdes if (pasv) { 49090267Sdes u_char addr[64]; 49190267Sdes char *ln, *p; 49290267Sdes unsigned int i; 49390267Sdes int port; 49437573Sdes 49590267Sdes /* send PASV command */ 49690267Sdes if (verbose) 49790267Sdes _fetch_info("setting passive mode"); 49890267Sdes switch (sa.ss_family) { 49990267Sdes case AF_INET: 50090267Sdes if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 50190267Sdes goto ouch; 50290267Sdes break; 50390267Sdes case AF_INET6: 50490267Sdes if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 50590267Sdes if (e == -1) 50690267Sdes goto ouch; 50790267Sdes if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 50890267Sdes goto ouch; 50990267Sdes } 51090267Sdes break; 51190267Sdes default: 51290267Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 51390267Sdes goto ouch; 51490267Sdes } 51537573Sdes 51690267Sdes /* 51790267Sdes * Find address and port number. The reply to the PASV command 51890267Sdes * is IMHO the one and only weak point in the FTP protocol. 51990267Sdes */ 52090267Sdes ln = last_reply; 52190267Sdes switch (e) { 52290267Sdes case FTP_PASSIVE_MODE: 52390267Sdes case FTP_LPASSIVE_MODE: 52490267Sdes for (p = ln + 3; *p && !isdigit(*p); p++) 52590267Sdes /* nothing */ ; 52690267Sdes if (!*p) { 52790267Sdes e = FTP_PROTOCOL_ERROR; 52890267Sdes goto ouch; 52990267Sdes } 53090267Sdes l = (e == FTP_PASSIVE_MODE ? 6 : 21); 53190267Sdes for (i = 0; *p && i < l; i++, p++) 53290267Sdes addr[i] = strtol(p, &p, 10); 53390267Sdes if (i < l) { 53490267Sdes e = FTP_PROTOCOL_ERROR; 53590267Sdes goto ouch; 53690267Sdes } 53790267Sdes break; 53890267Sdes case FTP_EPASSIVE_MODE: 53990267Sdes for (p = ln + 3; *p && *p != '('; p++) 54090267Sdes /* nothing */ ; 54190267Sdes if (!*p) { 54290267Sdes e = FTP_PROTOCOL_ERROR; 54390267Sdes goto ouch; 54490267Sdes } 54590267Sdes ++p; 54690267Sdes if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 54790267Sdes &port, &addr[3]) != 5 || 54890267Sdes addr[0] != addr[1] || 54990267Sdes addr[0] != addr[2] || addr[0] != addr[3]) { 55090267Sdes e = FTP_PROTOCOL_ERROR; 55190267Sdes goto ouch; 55290267Sdes } 55390267Sdes break; 55490267Sdes } 55560188Sdes 55690267Sdes /* seek to required offset */ 55790267Sdes if (offset) 55890267Sdes if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 55990267Sdes goto sysouch; 56090267Sdes 56190267Sdes /* construct sockaddr for data socket */ 56290267Sdes l = sizeof sa; 56390267Sdes if (getpeername(cd, (struct sockaddr *)&sa, &l) == -1) 56490267Sdes goto sysouch; 56590267Sdes if (sa.ss_family == AF_INET6) 56690267Sdes unmappedaddr((struct sockaddr_in6 *)&sa); 56790267Sdes switch (sa.ss_family) { 56890267Sdes case AF_INET6: 56990267Sdes sin6 = (struct sockaddr_in6 *)&sa; 57090267Sdes if (e == FTP_EPASSIVE_MODE) 57190267Sdes sin6->sin6_port = htons(port); 57290267Sdes else { 57390267Sdes bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 57490267Sdes bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 57590267Sdes } 57690267Sdes break; 57790267Sdes case AF_INET: 57890267Sdes sin4 = (struct sockaddr_in *)&sa; 57990267Sdes if (e == FTP_EPASSIVE_MODE) 58090267Sdes sin4->sin_port = htons(port); 58190267Sdes else { 58290267Sdes bcopy(addr, (char *)&sin4->sin_addr, 4); 58390267Sdes bcopy(addr + 4, (char *)&sin4->sin_port, 2); 58490267Sdes } 58590267Sdes break; 58690267Sdes default: 58790267Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 58890267Sdes break; 58990267Sdes } 59090267Sdes 59190267Sdes /* connect to data port */ 59290267Sdes if (verbose) 59390267Sdes _fetch_info("opening data connection"); 59490267Sdes if (connect(sd, (struct sockaddr *)&sa, sa.ss_len) == -1) 59590267Sdes goto sysouch; 59690267Sdes 59790267Sdes /* make the server initiate the transfer */ 59890267Sdes if (verbose) 59990267Sdes _fetch_info("initiating transfer"); 60090267Sdes e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 60190267Sdes if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) 60290267Sdes goto ouch; 60390267Sdes 60490267Sdes } else { 60590267Sdes u_int32_t a; 60690267Sdes u_short p; 60790267Sdes int arg, d; 60890267Sdes char *ap; 60990267Sdes char hname[INET6_ADDRSTRLEN]; 61090267Sdes 61190267Sdes switch (sa.ss_family) { 61290267Sdes case AF_INET6: 61390267Sdes ((struct sockaddr_in6 *)&sa)->sin6_port = 0; 61460737Sume#ifdef IPV6_PORTRANGE 61590267Sdes arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH; 61690267Sdes if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 61790267Sdes (char *)&arg, sizeof(arg)) == -1) 61890267Sdes goto sysouch; 61960737Sume#endif 62090267Sdes break; 62190267Sdes case AF_INET: 62290267Sdes ((struct sockaddr_in *)&sa)->sin_port = 0; 62390267Sdes arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH; 62490267Sdes if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 62590267Sdes (char *)&arg, sizeof arg) == -1) 62690267Sdes goto sysouch; 62790267Sdes break; 62890267Sdes } 62990267Sdes if (verbose) 63090267Sdes _fetch_info("binding data socket"); 63190267Sdes if (bind(sd, (struct sockaddr *)&sa, sa.ss_len) == -1) 63290267Sdes goto sysouch; 63390267Sdes if (listen(sd, 1) == -1) 63490267Sdes goto sysouch; 63537573Sdes 63690267Sdes /* find what port we're on and tell the server */ 63790267Sdes if (getsockname(sd, (struct sockaddr *)&sa, &l) == -1) 63890267Sdes goto sysouch; 63990267Sdes switch (sa.ss_family) { 64090267Sdes case AF_INET: 64190267Sdes sin4 = (struct sockaddr_in *)&sa; 64290267Sdes a = ntohl(sin4->sin_addr.s_addr); 64390267Sdes p = ntohs(sin4->sin_port); 64490267Sdes e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 64590267Sdes (a >> 24) & 0xff, (a >> 16) & 0xff, 64690267Sdes (a >> 8) & 0xff, a & 0xff, 64790267Sdes (p >> 8) & 0xff, p & 0xff); 64890267Sdes break; 64990267Sdes case AF_INET6: 65060737Sume#define UC(b) (((int)b)&0xff) 65190267Sdes e = -1; 65290267Sdes sin6 = (struct sockaddr_in6 *)&sa; 65390267Sdes if (getnameinfo((struct sockaddr *)&sa, sa.ss_len, 65490267Sdes hname, sizeof(hname), 65590267Sdes NULL, 0, NI_NUMERICHOST) == 0) { 65690267Sdes e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 65790267Sdes htons(sin6->sin6_port)); 65890267Sdes if (e == -1) 65990267Sdes goto ouch; 66090267Sdes } 66190267Sdes if (e != FTP_OK) { 66290267Sdes ap = (char *)&sin6->sin6_addr; 66390267Sdes e = _ftp_cmd(cd, 66490267Sdes "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 66590267Sdes 6, 16, 66690267Sdes UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 66790267Sdes UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 66890267Sdes UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 66990267Sdes UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 67090267Sdes 2, 67190267Sdes (ntohs(sin6->sin6_port) >> 8) & 0xff, 67290267Sdes ntohs(sin6->sin6_port) & 0xff); 67390267Sdes } 67490267Sdes break; 67590267Sdes default: 67690267Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 67790267Sdes goto ouch; 67890267Sdes } 67990267Sdes if (e != FTP_OK) 68090267Sdes goto ouch; 68190267Sdes 68290267Sdes /* seek to required offset */ 68390267Sdes if (offset) 68490267Sdes if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 68590267Sdes goto sysouch; 68690267Sdes 68790267Sdes /* make the server initiate the transfer */ 68890267Sdes if (verbose) 68990267Sdes _fetch_info("initiating transfer"); 69090267Sdes e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 69190267Sdes if (e != FTP_OPEN_DATA_CONNECTION) 69290267Sdes goto ouch; 69390267Sdes 69490267Sdes /* accept the incoming connection and go to town */ 69590267Sdes if ((d = accept(sd, NULL, NULL)) == -1) 69690267Sdes goto sysouch; 69790267Sdes close(sd); 69890267Sdes sd = d; 69960737Sume } 70037573Sdes 70190267Sdes if ((df = _ftp_setup(cd, sd, mode)) == NULL) 70262256Sdes goto sysouch; 70390267Sdes return (df); 70437573Sdes 70537573Sdessysouch: 70690267Sdes _fetch_syserr(); 70790267Sdes if (sd >= 0) 70890267Sdes close(sd); 70990267Sdes return (NULL); 71041869Sdes 71137573Sdesouch: 71290267Sdes if (e != -1) 71390267Sdes _ftp_seterr(e); 71490267Sdes if (sd >= 0) 71590267Sdes close(sd); 71690267Sdes return (NULL); 71737535Sdes} 71837535Sdes 71937571Sdes/* 72077238Sdes * Authenticate 72177238Sdes */ 72277238Sdesstatic int 72377238Sdes_ftp_authenticate(int cd, struct url *url, struct url *purl) 72477238Sdes{ 72590267Sdes const char *user, *pwd, *logname; 72690267Sdes char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 72790267Sdes int e, len; 72877238Sdes 72990267Sdes /* XXX FTP_AUTH, and maybe .netrc */ 73090267Sdes 73190267Sdes /* send user name and password */ 73290267Sdes user = url->user; 73390267Sdes if (!user || !*user) 73490267Sdes user = getenv("FTP_LOGIN"); 73590267Sdes if (!user || !*user) 73690267Sdes user = FTP_ANONYMOUS_USER; 73790267Sdes if (purl && url->port == _fetch_default_port(url->scheme)) 73890267Sdes e = _ftp_cmd(cd, "USER %s@%s", user, url->host); 73990267Sdes else if (purl) 74090267Sdes e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port); 74190267Sdes else 74290267Sdes e = _ftp_cmd(cd, "USER %s", user); 74390267Sdes 74490267Sdes /* did the server request a password? */ 74590267Sdes if (e == FTP_NEED_PASSWORD) { 74690267Sdes pwd = url->pwd; 74790267Sdes if (!pwd || !*pwd) 74890267Sdes pwd = getenv("FTP_PASSWORD"); 74990267Sdes if (!pwd || !*pwd) { 75090267Sdes if ((logname = getlogin()) == 0) 75190267Sdes logname = FTP_ANONYMOUS_USER; 75290267Sdes if ((len = snprintf(pbuf, MAXLOGNAME + 1, "%s@", logname)) < 0) 75390267Sdes len = 0; 75490267Sdes else if (len > MAXLOGNAME) 75590267Sdes len = MAXLOGNAME; 75690267Sdes gethostname(pbuf + len, sizeof pbuf - len); 75790267Sdes pwd = pbuf; 75890267Sdes } 75990267Sdes e = _ftp_cmd(cd, "PASS %s", pwd); 76077238Sdes } 76190267Sdes 76290267Sdes return (e); 76377238Sdes} 76477238Sdes 76577238Sdes/* 76637571Sdes * Log on to FTP server 76737535Sdes */ 76855557Sdesstatic int 76975891Sarchie_ftp_connect(struct url *url, struct url *purl, const char *flags) 77037571Sdes{ 77190267Sdes int cd, e, direct, verbose; 77260737Sume#ifdef INET6 77390267Sdes int af = AF_UNSPEC; 77460737Sume#else 77590267Sdes int af = AF_INET; 77660737Sume#endif 77737571Sdes 77890267Sdes direct = CHECK_FLAG('d'); 77990267Sdes verbose = CHECK_FLAG('v'); 78090267Sdes if (CHECK_FLAG('4')) 78190267Sdes af = AF_INET; 78290267Sdes else if (CHECK_FLAG('6')) 78390267Sdes af = AF_INET6; 78460737Sume 78590267Sdes if (direct) 78690267Sdes purl = NULL; 78737608Sdes 78890267Sdes /* check for proxy */ 78990267Sdes if (purl) { 79090267Sdes /* XXX proxy authentication! */ 79190267Sdes cd = _fetch_connect(purl->host, purl->port, af, verbose); 79290267Sdes } else { 79390267Sdes /* no proxy, go straight to target */ 79490267Sdes cd = _fetch_connect(url->host, url->port, af, verbose); 79590267Sdes purl = NULL; 79690267Sdes } 79737608Sdes 79890267Sdes /* check connection */ 79990267Sdes if (cd == -1) { 80090267Sdes _fetch_syserr(); 80190267Sdes return (NULL); 80290267Sdes } 80390267Sdes 80490267Sdes /* expect welcome message */ 80590267Sdes if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 80690267Sdes goto fouch; 80790267Sdes 80890267Sdes /* authenticate */ 80990267Sdes if ((e = _ftp_authenticate(cd, url, purl)) != FTP_LOGGED_IN) 81090267Sdes goto fouch; 81190267Sdes 81290267Sdes /* might as well select mode and type at once */ 81337571Sdes#ifdef FTP_FORCE_STREAM_MODE 81490267Sdes if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 81590267Sdes goto fouch; 81637571Sdes#endif 81790267Sdes if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 81890267Sdes goto fouch; 81937571Sdes 82090267Sdes /* done */ 82190267Sdes return (cd); 82290267Sdes 82337571Sdesfouch: 82490267Sdes if (e != -1) 82590267Sdes _ftp_seterr(e); 82690267Sdes close(cd); 82790267Sdes return (NULL); 82837571Sdes} 82937571Sdes 83037571Sdes/* 83137571Sdes * Disconnect from server 83237571Sdes */ 83337571Sdesstatic void 83455557Sdes_ftp_disconnect(int cd) 83537571Sdes{ 83690267Sdes (void)_ftp_cmd(cd, "QUIT"); 83790267Sdes close(cd); 83837571Sdes} 83937571Sdes 84037571Sdes/* 84137571Sdes * Check if we're already connected 84237571Sdes */ 84337571Sdesstatic int 84440975Sdes_ftp_isconnected(struct url *url) 84537571Sdes{ 84690267Sdes return (cached_socket 84737571Sdes && (strcmp(url->host, cached_host.host) == 0) 84837571Sdes && (strcmp(url->user, cached_host.user) == 0) 84937571Sdes && (strcmp(url->pwd, cached_host.pwd) == 0) 85037571Sdes && (url->port == cached_host.port)); 85137571Sdes} 85237571Sdes 85337608Sdes/* 85441869Sdes * Check the cache, reconnect if no luck 85537608Sdes */ 85655557Sdesstatic int 85775891Sarchie_ftp_cached_connect(struct url *url, struct url *purl, const char *flags) 85837535Sdes{ 85990267Sdes int e, cd; 86037535Sdes 86190267Sdes cd = -1; 86237571Sdes 86390267Sdes /* set default port */ 86490267Sdes if (!url->port) 86590267Sdes url->port = _fetch_default_port(url->scheme); 86690267Sdes 86790267Sdes /* try to use previously cached connection */ 86890267Sdes if (_ftp_isconnected(url)) { 86990267Sdes e = _ftp_cmd(cached_socket, "NOOP"); 87090267Sdes if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 87190267Sdes return (cached_socket); 87290267Sdes } 87390267Sdes 87490267Sdes /* connect to server */ 87590267Sdes if ((cd = _ftp_connect(url, purl, flags)) == -1) 87690267Sdes return (-1); 87790267Sdes if (cached_socket) 87890267Sdes _ftp_disconnect(cached_socket); 87990267Sdes cached_socket = cd; 88090267Sdes memcpy(&cached_host, url, sizeof *url); 88190267Sdes return (cd); 88237535Sdes} 88337535Sdes 88437571Sdes/* 88567043Sdes * Check the proxy settings 88663713Sdes */ 88767043Sdesstatic struct url * 88867043Sdes_ftp_get_proxy(void) 88963713Sdes{ 89090267Sdes struct url *purl; 89190267Sdes char *p; 89290267Sdes 89390267Sdes if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) || 89490267Sdes (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) && 89590267Sdes *p && (purl = fetchParseURL(p)) != NULL) { 89690267Sdes if (!*purl->scheme) { 89790267Sdes if (getenv("FTP_PROXY") || getenv("ftp_proxy")) 89890267Sdes strcpy(purl->scheme, SCHEME_FTP); 89990267Sdes else 90090267Sdes strcpy(purl->scheme, SCHEME_HTTP); 90190267Sdes } 90290267Sdes if (!purl->port) 90390267Sdes purl->port = _fetch_default_proxy_port(purl->scheme); 90490267Sdes if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 90590267Sdes strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 90690267Sdes return (purl); 90790267Sdes fetchFreeURL(purl); 90869272Sdes } 90990267Sdes return (NULL); 91063713Sdes} 91163713Sdes 91263713Sdes/* 91387315Sdes * Process an FTP request 91437571Sdes */ 91537535SdesFILE * 91687315Sdes_ftp_request(struct url *url, const char *op, struct url_stat *us, 91790267Sdes struct url *purl, const char *flags) 91837608Sdes{ 91990267Sdes int cd, oflag; 92090267Sdes 92190267Sdes /* check if we should use HTTP instead */ 92290267Sdes if (purl && strcasecmp(purl->scheme, SCHEME_HTTP) == 0) { 92390267Sdes if (strcmp(op, "STAT") == 0) 92490267Sdes return (_http_request(url, "HEAD", us, purl, flags)); 92590267Sdes else if (strcmp(op, "RETR") == 0) 92690267Sdes return (_http_request(url, "GET", us, purl, flags)); 92790267Sdes /* 92890267Sdes * Our HTTP code doesn't support PUT requests yet, so try 92990267Sdes * a direct connection. 93090267Sdes */ 93190267Sdes } 93290267Sdes 93390267Sdes /* connect to server */ 93490267Sdes cd = _ftp_cached_connect(url, purl, flags); 93590267Sdes if (purl) 93690267Sdes fetchFreeURL(purl); 93790267Sdes if (cd == NULL) 93890267Sdes return (NULL); 93990267Sdes 94090267Sdes /* change directory */ 94190267Sdes if (_ftp_cwd(cd, url->doc) == -1) 94290267Sdes return (NULL); 94390267Sdes 94490267Sdes /* stat file */ 94590267Sdes if (us && _ftp_stat(cd, url->doc, us) == -1 94690267Sdes && fetchLastErrCode != FETCH_PROTO 94790267Sdes && fetchLastErrCode != FETCH_UNAVAIL) 94890267Sdes return (NULL); 94990267Sdes 95090267Sdes /* just a stat */ 95187315Sdes if (strcmp(op, "STAT") == 0) 95290267Sdes return (FILE *)1; /* bogus return value */ 95390267Sdes if (strcmp(op, "STOR") == 0 || strcmp(op, "APPE") == 0) 95490267Sdes oflag = O_WRONLY; 95590267Sdes else 95690267Sdes oflag = O_RDONLY; 95787315Sdes 95890267Sdes /* initiate the transfer */ 95990267Sdes return (_ftp_transfer(cd, op, url->doc, oflag, url->offset, flags)); 96037608Sdes} 96137608Sdes 96241869Sdes/* 96387315Sdes * Get and stat file 96487315Sdes */ 96587315SdesFILE * 96687315SdesfetchXGetFTP(struct url *url, struct url_stat *us, const char *flags) 96787315Sdes{ 96890267Sdes return (_ftp_request(url, "RETR", us, _ftp_get_proxy(), flags)); 96987315Sdes} 97087315Sdes 97187315Sdes/* 97263340Sdes * Get file 97363340Sdes */ 97463340SdesFILE * 97575891SarchiefetchGetFTP(struct url *url, const char *flags) 97663340Sdes{ 97790267Sdes return (fetchXGetFTP(url, NULL, flags)); 97863340Sdes} 97963340Sdes 98063340Sdes/* 98141869Sdes * Put file 98241869Sdes */ 98337608SdesFILE * 98475891SarchiefetchPutFTP(struct url *url, const char *flags) 98537535Sdes{ 98641869Sdes 98790267Sdes return _ftp_request(url, CHECK_FLAG('a') ? "APPE" : "STOR", NULL, 98890267Sdes _ftp_get_proxy(), flags); 98937535Sdes} 99040975Sdes 99141869Sdes/* 99241869Sdes * Get file stats 99341869Sdes */ 99440975Sdesint 99575891SarchiefetchStatFTP(struct url *url, struct url_stat *us, const char *flags) 99640975Sdes{ 99790267Sdes 99890267Sdes if (_ftp_request(url, "STAT", us, _ftp_get_proxy(), flags) == NULL) 99990267Sdes return (-1); 100090267Sdes return (0); 100140975Sdes} 100241989Sdes 100341989Sdes/* 100441989Sdes * List a directory 100541989Sdes */ 100641989Sdesstruct url_ent * 100785093SdesfetchListFTP(struct url *url __unused, const char *flags __unused) 100841989Sdes{ 100990267Sdes warnx("fetchListFTP(): not implemented"); 101090267Sdes return (NULL); 101141989Sdes} 1012