ftp.c revision 97856
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 97856 2002-06-05 10:05:03Z des $"); 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> 6997856Sdes#include <stdint.h> 7037535Sdes#include <stdio.h> 7137571Sdes#include <stdlib.h> 7237535Sdes#include <string.h> 7341869Sdes#include <time.h> 7437571Sdes#include <unistd.h> 7537535Sdes 7637535Sdes#include "fetch.h" 7740939Sdes#include "common.h" 7841862Sdes#include "ftperr.h" 7937535Sdes 8070795Sdes#define FTP_ANONYMOUS_USER "anonymous" 8137535Sdes 8264883Sdes#define FTP_CONNECTION_ALREADY_OPEN 125 8337573Sdes#define FTP_OPEN_DATA_CONNECTION 150 8437573Sdes#define FTP_OK 200 8541869Sdes#define FTP_FILE_STATUS 213 8641863Sdes#define FTP_SERVICE_READY 220 8767890Sdes#define FTP_TRANSFER_COMPLETE 226 8837573Sdes#define FTP_PASSIVE_MODE 227 8960737Sume#define FTP_LPASSIVE_MODE 228 9060737Sume#define FTP_EPASSIVE_MODE 229 9137573Sdes#define FTP_LOGGED_IN 230 9237573Sdes#define FTP_FILE_ACTION_OK 250 9337573Sdes#define FTP_NEED_PASSWORD 331 9437573Sdes#define FTP_NEED_ACCOUNT 332 9560188Sdes#define FTP_FILE_OK 350 9655557Sdes#define FTP_SYNTAX_ERROR 500 9763336Sdes#define FTP_PROTOCOL_ERROR 999 9837573Sdes 9940975Sdesstatic struct url cached_host; 10097856Sdesstatic conn_t *cached_connection; 10137535Sdes 10255557Sdes#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 10360707Sdes && isdigit(foo[2]) \ 10490267Sdes && (foo[3] == ' ' || foo[3] == '\0')) 10555557Sdes#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 10655557Sdes && isdigit(foo[2]) && foo[3] == '-') 10755557Sdes 10890267Sdes/* 10990267Sdes * Translate IPv4 mapped IPv6 address to IPv4 address 11090267Sdes */ 11160737Sumestatic void 11260737Sumeunmappedaddr(struct sockaddr_in6 *sin6) 11360737Sume{ 11490267Sdes struct sockaddr_in *sin4; 11590267Sdes u_int32_t addr; 11690267Sdes int port; 11760737Sume 11890267Sdes if (sin6->sin6_family != AF_INET6 || 11990267Sdes !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 12090267Sdes return; 12190267Sdes sin4 = (struct sockaddr_in *)sin6; 12290267Sdes addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 12390267Sdes port = sin6->sin6_port; 12490267Sdes memset(sin4, 0, sizeof(struct sockaddr_in)); 12590267Sdes sin4->sin_addr.s_addr = addr; 12690267Sdes sin4->sin_port = port; 12790267Sdes sin4->sin_family = AF_INET; 12890267Sdes sin4->sin_len = sizeof(struct sockaddr_in); 12960737Sume} 13060737Sume 13137571Sdes/* 13255557Sdes * Get server response 13337535Sdes */ 13437535Sdesstatic int 13597856Sdes_ftp_chkerr(conn_t *conn) 13637535Sdes{ 13797856Sdes if (_fetch_getln(conn) == -1) { 13862215Sdes _fetch_syserr(); 13990267Sdes return (-1); 14062215Sdes } 14197856Sdes if (isftpinfo(conn->buf)) { 14297856Sdes while (conn->buflen && !isftpreply(conn->buf)) { 14397856Sdes if (_fetch_getln(conn) == -1) { 14490267Sdes _fetch_syserr(); 14590267Sdes return (-1); 14690267Sdes } 14790267Sdes } 14890267Sdes } 14955557Sdes 15097856Sdes while (conn->buflen && isspace(conn->buf[conn->buflen - 1])) 15197856Sdes conn->buflen--; 15297856Sdes conn->buf[conn->buflen] = '\0'; 15337535Sdes 15497856Sdes if (!isftpreply(conn->buf)) { 15590267Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 15690267Sdes return (-1); 15790267Sdes } 15855557Sdes 15997856Sdes conn->err = (conn->buf[0] - '0') * 100 16097856Sdes + (conn->buf[1] - '0') * 10 16197856Sdes + (conn->buf[2] - '0'); 16290267Sdes 16397856Sdes return (conn->err); 16437535Sdes} 16537535Sdes 16637535Sdes/* 16737573Sdes * Send a command and check reply 16837535Sdes */ 16937535Sdesstatic int 17097856Sdes_ftp_cmd(conn_t *conn, const char *fmt, ...) 17137535Sdes{ 17290267Sdes va_list ap; 17390267Sdes size_t len; 17490267Sdes char *msg; 17590267Sdes int r; 17637573Sdes 17790267Sdes va_start(ap, fmt); 17890267Sdes len = vasprintf(&msg, fmt, ap); 17990267Sdes va_end(ap); 18090267Sdes 18190267Sdes if (msg == NULL) { 18290267Sdes errno = ENOMEM; 18390267Sdes _fetch_syserr(); 18490267Sdes return (-1); 18590267Sdes } 18690267Sdes 18797856Sdes r = _fetch_putln(conn, msg, len); 18890267Sdes free(msg); 18990267Sdes 19090267Sdes if (r == -1) { 19190267Sdes _fetch_syserr(); 19290267Sdes return (-1); 19390267Sdes } 19490267Sdes 19597856Sdes return (_ftp_chkerr(conn)); 19637535Sdes} 19737535Sdes 19837535Sdes/* 19963340Sdes * Return a pointer to the filename part of a path 20063340Sdes */ 20175891Sarchiestatic const char * 20275891Sarchie_ftp_filename(const char *file) 20363340Sdes{ 20490267Sdes char *s; 20590267Sdes 20690267Sdes if ((s = strrchr(file, '/')) == NULL) 20790267Sdes return (file); 20890267Sdes else 20990267Sdes return (s + 1); 21063340Sdes} 21163340Sdes 21263340Sdes/* 21390267Sdes * Change working directory to the directory that contains the specified 21490267Sdes * file. 21563340Sdes */ 21663340Sdesstatic int 21797856Sdes_ftp_cwd(conn_t *conn, const char *file) 21863340Sdes{ 21990267Sdes char *s; 22090267Sdes int e; 22163340Sdes 22290267Sdes if ((s = strrchr(file, '/')) == NULL || s == file) { 22397856Sdes e = _ftp_cmd(conn, "CWD /"); 22490267Sdes } else { 22597856Sdes e = _ftp_cmd(conn, "CWD %.*s", s - file, file); 22690267Sdes } 22790267Sdes if (e != FTP_FILE_ACTION_OK) { 22890267Sdes _ftp_seterr(e); 22990267Sdes return (-1); 23090267Sdes } 23190267Sdes return (0); 23263340Sdes} 23363340Sdes 23463340Sdes/* 23563340Sdes * Request and parse file stats 23663340Sdes */ 23763340Sdesstatic int 23897856Sdes_ftp_stat(conn_t *conn, const char *file, struct url_stat *us) 23963340Sdes{ 24090267Sdes char *ln; 24190267Sdes const char *s; 24290267Sdes struct tm tm; 24390267Sdes time_t t; 24490267Sdes int e; 24563340Sdes 24675292Sdes us->size = -1; 24790267Sdes us->atime = us->mtime = 0; 24863340Sdes 24990267Sdes if ((s = strrchr(file, '/')) == NULL) 25090267Sdes s = file; 25190267Sdes else 25290267Sdes ++s; 25390267Sdes 25497856Sdes if ((e = _ftp_cmd(conn, "SIZE %s", s)) != FTP_FILE_STATUS) { 25590267Sdes _ftp_seterr(e); 25690267Sdes return (-1); 25790267Sdes } 25897856Sdes for (ln = conn->buf + 4; *ln && isspace(*ln); ln++) 25990267Sdes /* nothing */ ; 26090267Sdes for (us->size = 0; *ln && isdigit(*ln); ln++) 26190267Sdes us->size = us->size * 10 + *ln - '0'; 26290267Sdes if (*ln && !isspace(*ln)) { 26390267Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 26490267Sdes us->size = -1; 26590267Sdes return (-1); 26690267Sdes } 26790267Sdes if (us->size == 0) 26890267Sdes us->size = -1; 26990267Sdes DEBUG(fprintf(stderr, "size: [%lld]\n", (long long)us->size)); 27090267Sdes 27197856Sdes if ((e = _ftp_cmd(conn, "MDTM %s", s)) != FTP_FILE_STATUS) { 27290267Sdes _ftp_seterr(e); 27390267Sdes return (-1); 27490267Sdes } 27597856Sdes for (ln = conn->buf + 4; *ln && isspace(*ln); ln++) 27690267Sdes /* nothing */ ; 27790267Sdes switch (strspn(ln, "0123456789")) { 27890267Sdes case 14: 27990267Sdes break; 28090267Sdes case 15: 28190267Sdes ln++; 28290267Sdes ln[0] = '2'; 28390267Sdes ln[1] = '0'; 28490267Sdes break; 28590267Sdes default: 28690267Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 28790267Sdes return (-1); 28890267Sdes } 28990267Sdes if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 29090267Sdes &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 29190267Sdes &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { 29290267Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 29390267Sdes return (-1); 29490267Sdes } 29590267Sdes tm.tm_mon--; 29690267Sdes tm.tm_year -= 1900; 29790267Sdes tm.tm_isdst = -1; 29890267Sdes t = timegm(&tm); 29990267Sdes if (t == (time_t)-1) 30090267Sdes t = time(NULL); 30190267Sdes us->mtime = t; 30290267Sdes us->atime = t; 30390267Sdes DEBUG(fprintf(stderr, 30490267Sdes "last modified: [%04d-%02d-%02d %02d:%02d:%02d]\n", 30590267Sdes tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 30690267Sdes tm.tm_hour, tm.tm_min, tm.tm_sec)); 30790267Sdes return (0); 30863340Sdes} 30963340Sdes 31063340Sdes/* 31167430Sdes * I/O functions for FTP 31267430Sdes */ 31367430Sdesstruct ftpio { 31497856Sdes conn_t *conn; /* Control connection */ 31590267Sdes int dsd; /* Data socket descriptor */ 31690267Sdes int dir; /* Direction */ 31790267Sdes int eof; /* EOF reached */ 31890267Sdes int err; /* Error code */ 31967430Sdes}; 32067430Sdes 32190267Sdesstatic int _ftp_readfn(void *, char *, int); 32290267Sdesstatic int _ftp_writefn(void *, const char *, int); 32390267Sdesstatic fpos_t _ftp_seekfn(void *, fpos_t, int); 32490267Sdesstatic int _ftp_closefn(void *); 32567430Sdes 32667430Sdesstatic int 32767430Sdes_ftp_readfn(void *v, char *buf, int len) 32867430Sdes{ 32990267Sdes struct ftpio *io; 33090267Sdes int r; 33167430Sdes 33290267Sdes io = (struct ftpio *)v; 33390267Sdes if (io == NULL) { 33490267Sdes errno = EBADF; 33590267Sdes return (-1); 33690267Sdes } 33797856Sdes if (io->conn == NULL || io->dsd == -1 || io->dir == O_WRONLY) { 33890267Sdes errno = EBADF; 33990267Sdes return (-1); 34090267Sdes } 34190267Sdes if (io->err) { 34290267Sdes errno = io->err; 34390267Sdes return (-1); 34490267Sdes } 34590267Sdes if (io->eof) 34690267Sdes return (0); 34790267Sdes r = read(io->dsd, buf, len); 34890267Sdes if (r > 0) 34990267Sdes return (r); 35090267Sdes if (r == 0) { 35190267Sdes io->eof = 1; 35290267Sdes return (0); 35390267Sdes } 35490267Sdes if (errno != EINTR) 35590267Sdes io->err = errno; 35690267Sdes return (-1); 35767430Sdes} 35867430Sdes 35967430Sdesstatic int 36067430Sdes_ftp_writefn(void *v, const char *buf, int len) 36167430Sdes{ 36290267Sdes struct ftpio *io; 36390267Sdes int w; 36490267Sdes 36590267Sdes io = (struct ftpio *)v; 36690267Sdes if (io == NULL) { 36790267Sdes errno = EBADF; 36890267Sdes return (-1); 36990267Sdes } 37097856Sdes if (io->conn == NULL || io->dsd == -1 || io->dir == O_RDONLY) { 37190267Sdes errno = EBADF; 37290267Sdes return (-1); 37390267Sdes } 37490267Sdes if (io->err) { 37590267Sdes errno = io->err; 37690267Sdes return (-1); 37790267Sdes } 37890267Sdes w = write(io->dsd, buf, len); 37990267Sdes if (w >= 0) 38090267Sdes return (w); 38190267Sdes if (errno != EINTR) 38290267Sdes io->err = errno; 38390267Sdes return (-1); 38467430Sdes} 38567430Sdes 38667430Sdesstatic fpos_t 38785093Sdes_ftp_seekfn(void *v, fpos_t pos __unused, int whence __unused) 38867430Sdes{ 38990267Sdes struct ftpio *io; 39090267Sdes 39190267Sdes io = (struct ftpio *)v; 39290267Sdes if (io == NULL) { 39390267Sdes errno = EBADF; 39490267Sdes return (-1); 39590267Sdes } 39690267Sdes errno = ESPIPE; 39790267Sdes return (-1); 39867430Sdes} 39967430Sdes 40067430Sdesstatic int 40167430Sdes_ftp_closefn(void *v) 40267430Sdes{ 40390267Sdes struct ftpio *io; 40490267Sdes int r; 40567430Sdes 40690267Sdes io = (struct ftpio *)v; 40790267Sdes if (io == NULL) { 40890267Sdes errno = EBADF; 40990267Sdes return (-1); 41090267Sdes } 41190267Sdes if (io->dir == -1) 41290267Sdes return (0); 41397856Sdes if (io->conn == NULL || io->dsd == -1) { 41490267Sdes errno = EBADF; 41590267Sdes return (-1); 41690267Sdes } 41790267Sdes close(io->dsd); 41890267Sdes io->dir = -1; 41990267Sdes io->dsd = -1; 42090267Sdes DEBUG(fprintf(stderr, "Waiting for final status\n")); 42197856Sdes r = _ftp_chkerr(io->conn); 42297856Sdes _fetch_close(io->conn); 42390267Sdes free(io); 42490267Sdes return (r == FTP_TRANSFER_COMPLETE) ? 0 : -1; 42567430Sdes} 42667430Sdes 42767430Sdesstatic FILE * 42897856Sdes_ftp_setup(conn_t *conn, int dsd, int mode) 42967430Sdes{ 43090267Sdes struct ftpio *io; 43190267Sdes FILE *f; 43267430Sdes 43390267Sdes if ((io = malloc(sizeof *io)) == NULL) 43490267Sdes return (NULL); 43597856Sdes io->conn = conn; 43690267Sdes io->dsd = dsd; 43790267Sdes io->dir = mode; 43890267Sdes io->eof = io->err = 0; 43990267Sdes f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn); 44090267Sdes if (f == NULL) 44190267Sdes free(io); 44290267Sdes return (f); 44367430Sdes} 44467430Sdes 44567430Sdes/* 44637608Sdes * Transfer file 44737535Sdes */ 44837535Sdesstatic FILE * 44997856Sdes_ftp_transfer(conn_t *conn, const char *oper, const char *file, 45090267Sdes int mode, off_t offset, const char *flags) 45137535Sdes{ 45290267Sdes struct sockaddr_storage sa; 45390267Sdes struct sockaddr_in6 *sin6; 45490267Sdes struct sockaddr_in *sin4; 45590267Sdes int low, pasv, verbose; 45690267Sdes int e, sd = -1; 45790267Sdes socklen_t l; 45890267Sdes char *s; 45990267Sdes FILE *df; 46055544Sdes 46190267Sdes /* check flags */ 46290267Sdes low = CHECK_FLAG('l'); 46390267Sdes pasv = CHECK_FLAG('p'); 46490267Sdes verbose = CHECK_FLAG('v'); 46555544Sdes 46690267Sdes /* passive mode */ 46790267Sdes if (!pasv) 46890267Sdes pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL && 46990267Sdes strncasecmp(s, "no", 2) != 0); 47060951Sdes 47190267Sdes /* find our own address, bind, and listen */ 47290267Sdes l = sizeof sa; 47397856Sdes if (getsockname(conn->sd, (struct sockaddr *)&sa, &l) == -1) 47490267Sdes goto sysouch; 47590267Sdes if (sa.ss_family == AF_INET6) 47690267Sdes unmappedaddr((struct sockaddr_in6 *)&sa); 47760737Sume 47890267Sdes /* open data socket */ 47990267Sdes if ((sd = socket(sa.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 48090267Sdes _fetch_syserr(); 48190267Sdes return (NULL); 48260737Sume } 48337573Sdes 48490267Sdes if (pasv) { 48590267Sdes u_char addr[64]; 48690267Sdes char *ln, *p; 48790267Sdes unsigned int i; 48890267Sdes int port; 48937573Sdes 49090267Sdes /* send PASV command */ 49190267Sdes if (verbose) 49290267Sdes _fetch_info("setting passive mode"); 49390267Sdes switch (sa.ss_family) { 49490267Sdes case AF_INET: 49597856Sdes if ((e = _ftp_cmd(conn, "PASV")) != FTP_PASSIVE_MODE) 49690267Sdes goto ouch; 49790267Sdes break; 49890267Sdes case AF_INET6: 49997856Sdes if ((e = _ftp_cmd(conn, "EPSV")) != FTP_EPASSIVE_MODE) { 50090267Sdes if (e == -1) 50190267Sdes goto ouch; 50297856Sdes if ((e = _ftp_cmd(conn, "LPSV")) != 50397856Sdes FTP_LPASSIVE_MODE) 50490267Sdes goto ouch; 50590267Sdes } 50690267Sdes break; 50790267Sdes default: 50890267Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 50990267Sdes goto ouch; 51090267Sdes } 51137573Sdes 51290267Sdes /* 51390267Sdes * Find address and port number. The reply to the PASV command 51490267Sdes * is IMHO the one and only weak point in the FTP protocol. 51590267Sdes */ 51697856Sdes ln = conn->buf; 51790267Sdes switch (e) { 51890267Sdes case FTP_PASSIVE_MODE: 51990267Sdes case FTP_LPASSIVE_MODE: 52090267Sdes for (p = ln + 3; *p && !isdigit(*p); p++) 52190267Sdes /* nothing */ ; 52290267Sdes if (!*p) { 52390267Sdes e = FTP_PROTOCOL_ERROR; 52490267Sdes goto ouch; 52590267Sdes } 52690267Sdes l = (e == FTP_PASSIVE_MODE ? 6 : 21); 52790267Sdes for (i = 0; *p && i < l; i++, p++) 52890267Sdes addr[i] = strtol(p, &p, 10); 52990267Sdes if (i < l) { 53090267Sdes e = FTP_PROTOCOL_ERROR; 53190267Sdes goto ouch; 53290267Sdes } 53390267Sdes break; 53490267Sdes case FTP_EPASSIVE_MODE: 53590267Sdes for (p = ln + 3; *p && *p != '('; p++) 53690267Sdes /* nothing */ ; 53790267Sdes if (!*p) { 53890267Sdes e = FTP_PROTOCOL_ERROR; 53990267Sdes goto ouch; 54090267Sdes } 54190267Sdes ++p; 54290267Sdes if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 54390267Sdes &port, &addr[3]) != 5 || 54490267Sdes addr[0] != addr[1] || 54590267Sdes addr[0] != addr[2] || addr[0] != addr[3]) { 54690267Sdes e = FTP_PROTOCOL_ERROR; 54790267Sdes goto ouch; 54890267Sdes } 54990267Sdes break; 55090267Sdes } 55160188Sdes 55290267Sdes /* seek to required offset */ 55390267Sdes if (offset) 55497856Sdes if (_ftp_cmd(conn, "REST %lu", (u_long)offset) != FTP_FILE_OK) 55590267Sdes goto sysouch; 55690267Sdes 55790267Sdes /* construct sockaddr for data socket */ 55890267Sdes l = sizeof sa; 55997856Sdes if (getpeername(conn->sd, (struct sockaddr *)&sa, &l) == -1) 56090267Sdes goto sysouch; 56190267Sdes if (sa.ss_family == AF_INET6) 56290267Sdes unmappedaddr((struct sockaddr_in6 *)&sa); 56390267Sdes switch (sa.ss_family) { 56490267Sdes case AF_INET6: 56590267Sdes sin6 = (struct sockaddr_in6 *)&sa; 56690267Sdes if (e == FTP_EPASSIVE_MODE) 56790267Sdes sin6->sin6_port = htons(port); 56890267Sdes else { 56990267Sdes bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 57090267Sdes bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 57190267Sdes } 57290267Sdes break; 57390267Sdes case AF_INET: 57490267Sdes sin4 = (struct sockaddr_in *)&sa; 57590267Sdes if (e == FTP_EPASSIVE_MODE) 57690267Sdes sin4->sin_port = htons(port); 57790267Sdes else { 57890267Sdes bcopy(addr, (char *)&sin4->sin_addr, 4); 57990267Sdes bcopy(addr + 4, (char *)&sin4->sin_port, 2); 58090267Sdes } 58190267Sdes break; 58290267Sdes default: 58390267Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 58490267Sdes break; 58590267Sdes } 58690267Sdes 58790267Sdes /* connect to data port */ 58890267Sdes if (verbose) 58990267Sdes _fetch_info("opening data connection"); 59090267Sdes if (connect(sd, (struct sockaddr *)&sa, sa.ss_len) == -1) 59190267Sdes goto sysouch; 59290267Sdes 59390267Sdes /* make the server initiate the transfer */ 59490267Sdes if (verbose) 59590267Sdes _fetch_info("initiating transfer"); 59697856Sdes e = _ftp_cmd(conn, "%s %s", oper, _ftp_filename(file)); 59790267Sdes if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) 59890267Sdes goto ouch; 59990267Sdes 60090267Sdes } else { 60190267Sdes u_int32_t a; 60290267Sdes u_short p; 60390267Sdes int arg, d; 60490267Sdes char *ap; 60590267Sdes char hname[INET6_ADDRSTRLEN]; 60690267Sdes 60790267Sdes switch (sa.ss_family) { 60890267Sdes case AF_INET6: 60990267Sdes ((struct sockaddr_in6 *)&sa)->sin6_port = 0; 61060737Sume#ifdef IPV6_PORTRANGE 61190267Sdes arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH; 61290267Sdes if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 61390267Sdes (char *)&arg, sizeof(arg)) == -1) 61490267Sdes goto sysouch; 61560737Sume#endif 61690267Sdes break; 61790267Sdes case AF_INET: 61890267Sdes ((struct sockaddr_in *)&sa)->sin_port = 0; 61990267Sdes arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH; 62090267Sdes if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 62190267Sdes (char *)&arg, sizeof arg) == -1) 62290267Sdes goto sysouch; 62390267Sdes break; 62490267Sdes } 62590267Sdes if (verbose) 62690267Sdes _fetch_info("binding data socket"); 62790267Sdes if (bind(sd, (struct sockaddr *)&sa, sa.ss_len) == -1) 62890267Sdes goto sysouch; 62990267Sdes if (listen(sd, 1) == -1) 63090267Sdes goto sysouch; 63137573Sdes 63290267Sdes /* find what port we're on and tell the server */ 63390267Sdes if (getsockname(sd, (struct sockaddr *)&sa, &l) == -1) 63490267Sdes goto sysouch; 63590267Sdes switch (sa.ss_family) { 63690267Sdes case AF_INET: 63790267Sdes sin4 = (struct sockaddr_in *)&sa; 63890267Sdes a = ntohl(sin4->sin_addr.s_addr); 63990267Sdes p = ntohs(sin4->sin_port); 64097856Sdes e = _ftp_cmd(conn, "PORT %d,%d,%d,%d,%d,%d", 64190267Sdes (a >> 24) & 0xff, (a >> 16) & 0xff, 64290267Sdes (a >> 8) & 0xff, a & 0xff, 64390267Sdes (p >> 8) & 0xff, p & 0xff); 64490267Sdes break; 64590267Sdes case AF_INET6: 64660737Sume#define UC(b) (((int)b)&0xff) 64790267Sdes e = -1; 64890267Sdes sin6 = (struct sockaddr_in6 *)&sa; 64990267Sdes if (getnameinfo((struct sockaddr *)&sa, sa.ss_len, 65090267Sdes hname, sizeof(hname), 65190267Sdes NULL, 0, NI_NUMERICHOST) == 0) { 65297856Sdes e = _ftp_cmd(conn, "EPRT |%d|%s|%d|", 2, hname, 65390267Sdes htons(sin6->sin6_port)); 65490267Sdes if (e == -1) 65590267Sdes goto ouch; 65690267Sdes } 65790267Sdes if (e != FTP_OK) { 65890267Sdes ap = (char *)&sin6->sin6_addr; 65997856Sdes e = _ftp_cmd(conn, 66090267Sdes "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 66190267Sdes 6, 16, 66290267Sdes UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 66390267Sdes UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 66490267Sdes UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 66590267Sdes UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 66690267Sdes 2, 66790267Sdes (ntohs(sin6->sin6_port) >> 8) & 0xff, 66890267Sdes ntohs(sin6->sin6_port) & 0xff); 66990267Sdes } 67090267Sdes break; 67190267Sdes default: 67290267Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 67390267Sdes goto ouch; 67490267Sdes } 67590267Sdes if (e != FTP_OK) 67690267Sdes goto ouch; 67790267Sdes 67890267Sdes /* seek to required offset */ 67990267Sdes if (offset) 68097856Sdes if (_ftp_cmd(conn, "REST %ju", (uintmax_t)offset) != FTP_FILE_OK) 68190267Sdes goto sysouch; 68290267Sdes 68390267Sdes /* make the server initiate the transfer */ 68490267Sdes if (verbose) 68590267Sdes _fetch_info("initiating transfer"); 68697856Sdes e = _ftp_cmd(conn, "%s %s", oper, _ftp_filename(file)); 68790267Sdes if (e != FTP_OPEN_DATA_CONNECTION) 68890267Sdes goto ouch; 68990267Sdes 69090267Sdes /* accept the incoming connection and go to town */ 69190267Sdes if ((d = accept(sd, NULL, NULL)) == -1) 69290267Sdes goto sysouch; 69390267Sdes close(sd); 69490267Sdes sd = d; 69560737Sume } 69637573Sdes 69797856Sdes if ((df = _ftp_setup(conn, sd, mode)) == NULL) 69862256Sdes goto sysouch; 69990267Sdes return (df); 70037573Sdes 70137573Sdessysouch: 70290267Sdes _fetch_syserr(); 70390267Sdes if (sd >= 0) 70490267Sdes close(sd); 70590267Sdes return (NULL); 70641869Sdes 70737573Sdesouch: 70890267Sdes if (e != -1) 70990267Sdes _ftp_seterr(e); 71090267Sdes if (sd >= 0) 71190267Sdes close(sd); 71290267Sdes return (NULL); 71337535Sdes} 71437535Sdes 71537571Sdes/* 71677238Sdes * Authenticate 71777238Sdes */ 71877238Sdesstatic int 71997856Sdes_ftp_authenticate(conn_t *conn, struct url *url, struct url *purl) 72077238Sdes{ 72190267Sdes const char *user, *pwd, *logname; 72290267Sdes char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 72390267Sdes int e, len; 72477238Sdes 72590267Sdes /* XXX FTP_AUTH, and maybe .netrc */ 72690267Sdes 72790267Sdes /* send user name and password */ 72890267Sdes user = url->user; 72990267Sdes if (!user || !*user) 73090267Sdes user = getenv("FTP_LOGIN"); 73190267Sdes if (!user || !*user) 73290267Sdes user = FTP_ANONYMOUS_USER; 73390267Sdes if (purl && url->port == _fetch_default_port(url->scheme)) 73497856Sdes e = _ftp_cmd(conn, "USER %s@%s", user, url->host); 73590267Sdes else if (purl) 73697856Sdes e = _ftp_cmd(conn, "USER %s@%s@%d", user, url->host, url->port); 73790267Sdes else 73897856Sdes e = _ftp_cmd(conn, "USER %s", user); 73990267Sdes 74090267Sdes /* did the server request a password? */ 74190267Sdes if (e == FTP_NEED_PASSWORD) { 74290267Sdes pwd = url->pwd; 74390267Sdes if (!pwd || !*pwd) 74490267Sdes pwd = getenv("FTP_PASSWORD"); 74590267Sdes if (!pwd || !*pwd) { 74690267Sdes if ((logname = getlogin()) == 0) 74790267Sdes logname = FTP_ANONYMOUS_USER; 74890267Sdes if ((len = snprintf(pbuf, MAXLOGNAME + 1, "%s@", logname)) < 0) 74990267Sdes len = 0; 75090267Sdes else if (len > MAXLOGNAME) 75190267Sdes len = MAXLOGNAME; 75290267Sdes gethostname(pbuf + len, sizeof pbuf - len); 75390267Sdes pwd = pbuf; 75490267Sdes } 75597856Sdes e = _ftp_cmd(conn, "PASS %s", pwd); 75677238Sdes } 75790267Sdes 75890267Sdes return (e); 75977238Sdes} 76077238Sdes 76177238Sdes/* 76237571Sdes * Log on to FTP server 76337535Sdes */ 76497856Sdesstatic conn_t * 76575891Sarchie_ftp_connect(struct url *url, struct url *purl, const char *flags) 76637571Sdes{ 76797856Sdes conn_t *conn; 76897856Sdes int e, direct, verbose; 76960737Sume#ifdef INET6 77090267Sdes int af = AF_UNSPEC; 77160737Sume#else 77290267Sdes int af = AF_INET; 77360737Sume#endif 77437571Sdes 77590267Sdes direct = CHECK_FLAG('d'); 77690267Sdes verbose = CHECK_FLAG('v'); 77790267Sdes if (CHECK_FLAG('4')) 77890267Sdes af = AF_INET; 77990267Sdes else if (CHECK_FLAG('6')) 78090267Sdes af = AF_INET6; 78160737Sume 78290267Sdes if (direct) 78390267Sdes purl = NULL; 78437608Sdes 78590267Sdes /* check for proxy */ 78690267Sdes if (purl) { 78790267Sdes /* XXX proxy authentication! */ 78897856Sdes conn = _fetch_connect(purl->host, purl->port, af, verbose); 78990267Sdes } else { 79090267Sdes /* no proxy, go straight to target */ 79197856Sdes conn = _fetch_connect(url->host, url->port, af, verbose); 79290267Sdes purl = NULL; 79390267Sdes } 79437608Sdes 79590267Sdes /* check connection */ 79697856Sdes if (conn == NULL) { 79790267Sdes _fetch_syserr(); 79890267Sdes return (NULL); 79990267Sdes } 80090267Sdes 80190267Sdes /* expect welcome message */ 80297856Sdes if ((e = _ftp_chkerr(conn)) != FTP_SERVICE_READY) 80390267Sdes goto fouch; 80490267Sdes 80590267Sdes /* authenticate */ 80697856Sdes if ((e = _ftp_authenticate(conn, url, purl)) != FTP_LOGGED_IN) 80790267Sdes goto fouch; 80890267Sdes 80990267Sdes /* might as well select mode and type at once */ 81037571Sdes#ifdef FTP_FORCE_STREAM_MODE 81197856Sdes if ((e = _ftp_cmd(conn, "MODE S")) != FTP_OK) /* default is S */ 81290267Sdes goto fouch; 81337571Sdes#endif 81497856Sdes if ((e = _ftp_cmd(conn, "TYPE I")) != FTP_OK) /* default is A */ 81590267Sdes goto fouch; 81637571Sdes 81790267Sdes /* done */ 81897856Sdes return (conn); 81990267Sdes 82037571Sdesfouch: 82190267Sdes if (e != -1) 82290267Sdes _ftp_seterr(e); 82397856Sdes _fetch_close(conn); 82490267Sdes return (NULL); 82537571Sdes} 82637571Sdes 82737571Sdes/* 82837571Sdes * Disconnect from server 82937571Sdes */ 83037571Sdesstatic void 83197856Sdes_ftp_disconnect(conn_t *conn) 83237571Sdes{ 83397856Sdes (void)_ftp_cmd(conn, "QUIT"); 83497856Sdes _fetch_close(conn); 83537571Sdes} 83637571Sdes 83737571Sdes/* 83837571Sdes * Check if we're already connected 83937571Sdes */ 84037571Sdesstatic int 84140975Sdes_ftp_isconnected(struct url *url) 84237571Sdes{ 84397856Sdes return (cached_connection 84437571Sdes && (strcmp(url->host, cached_host.host) == 0) 84537571Sdes && (strcmp(url->user, cached_host.user) == 0) 84637571Sdes && (strcmp(url->pwd, cached_host.pwd) == 0) 84737571Sdes && (url->port == cached_host.port)); 84837571Sdes} 84937571Sdes 85037608Sdes/* 85141869Sdes * Check the cache, reconnect if no luck 85237608Sdes */ 85397856Sdesstatic conn_t * 85475891Sarchie_ftp_cached_connect(struct url *url, struct url *purl, const char *flags) 85537535Sdes{ 85697856Sdes conn_t *conn; 85797856Sdes int e; 85837535Sdes 85990267Sdes /* set default port */ 86090267Sdes if (!url->port) 86190267Sdes url->port = _fetch_default_port(url->scheme); 86290267Sdes 86390267Sdes /* try to use previously cached connection */ 86490267Sdes if (_ftp_isconnected(url)) { 86597856Sdes e = _ftp_cmd(cached_connection, "NOOP"); 86690267Sdes if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 86797856Sdes return (cached_connection); 86890267Sdes } 86990267Sdes 87090267Sdes /* connect to server */ 87197856Sdes if ((conn = _ftp_connect(url, purl, flags)) == NULL) 87297856Sdes return (NULL); 87397856Sdes if (cached_connection) 87497856Sdes _ftp_disconnect(cached_connection); 87597856Sdes cached_connection = conn; 87690267Sdes memcpy(&cached_host, url, sizeof *url); 87797856Sdes return (conn); 87837535Sdes} 87937535Sdes 88037571Sdes/* 88167043Sdes * Check the proxy settings 88263713Sdes */ 88367043Sdesstatic struct url * 88467043Sdes_ftp_get_proxy(void) 88563713Sdes{ 88690267Sdes struct url *purl; 88790267Sdes char *p; 88890267Sdes 88990267Sdes if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) || 89090267Sdes (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) && 89190267Sdes *p && (purl = fetchParseURL(p)) != NULL) { 89290267Sdes if (!*purl->scheme) { 89390267Sdes if (getenv("FTP_PROXY") || getenv("ftp_proxy")) 89490267Sdes strcpy(purl->scheme, SCHEME_FTP); 89590267Sdes else 89690267Sdes strcpy(purl->scheme, SCHEME_HTTP); 89790267Sdes } 89890267Sdes if (!purl->port) 89990267Sdes purl->port = _fetch_default_proxy_port(purl->scheme); 90090267Sdes if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 90190267Sdes strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 90290267Sdes return (purl); 90390267Sdes fetchFreeURL(purl); 90469272Sdes } 90590267Sdes return (NULL); 90663713Sdes} 90763713Sdes 90863713Sdes/* 90987315Sdes * Process an FTP request 91037571Sdes */ 91137535SdesFILE * 91287315Sdes_ftp_request(struct url *url, const char *op, struct url_stat *us, 91390267Sdes struct url *purl, const char *flags) 91437608Sdes{ 91597856Sdes conn_t *conn; 91697856Sdes int oflag; 91790267Sdes 91890267Sdes /* check if we should use HTTP instead */ 91990267Sdes if (purl && strcasecmp(purl->scheme, SCHEME_HTTP) == 0) { 92090267Sdes if (strcmp(op, "STAT") == 0) 92190267Sdes return (_http_request(url, "HEAD", us, purl, flags)); 92290267Sdes else if (strcmp(op, "RETR") == 0) 92390267Sdes return (_http_request(url, "GET", us, purl, flags)); 92490267Sdes /* 92590267Sdes * Our HTTP code doesn't support PUT requests yet, so try 92690267Sdes * a direct connection. 92790267Sdes */ 92890267Sdes } 92990267Sdes 93090267Sdes /* connect to server */ 93197856Sdes conn = _ftp_cached_connect(url, purl, flags); 93290267Sdes if (purl) 93390267Sdes fetchFreeURL(purl); 93497856Sdes if (conn == NULL) 93590267Sdes return (NULL); 93690267Sdes 93790267Sdes /* change directory */ 93897856Sdes if (_ftp_cwd(conn, url->doc) == -1) 93990267Sdes return (NULL); 94090267Sdes 94190267Sdes /* stat file */ 94297856Sdes if (us && _ftp_stat(conn, url->doc, us) == -1 94390267Sdes && fetchLastErrCode != FETCH_PROTO 94490267Sdes && fetchLastErrCode != FETCH_UNAVAIL) 94590267Sdes return (NULL); 94690267Sdes 94790267Sdes /* just a stat */ 94887315Sdes if (strcmp(op, "STAT") == 0) 94990267Sdes return (FILE *)1; /* bogus return value */ 95090267Sdes if (strcmp(op, "STOR") == 0 || strcmp(op, "APPE") == 0) 95190267Sdes oflag = O_WRONLY; 95290267Sdes else 95390267Sdes oflag = O_RDONLY; 95487315Sdes 95590267Sdes /* initiate the transfer */ 95697856Sdes return (_ftp_transfer(conn, op, url->doc, oflag, url->offset, flags)); 95737608Sdes} 95837608Sdes 95941869Sdes/* 96087315Sdes * Get and stat file 96187315Sdes */ 96287315SdesFILE * 96387315SdesfetchXGetFTP(struct url *url, struct url_stat *us, const char *flags) 96487315Sdes{ 96590267Sdes return (_ftp_request(url, "RETR", us, _ftp_get_proxy(), flags)); 96687315Sdes} 96787315Sdes 96887315Sdes/* 96963340Sdes * Get file 97063340Sdes */ 97163340SdesFILE * 97275891SarchiefetchGetFTP(struct url *url, const char *flags) 97363340Sdes{ 97490267Sdes return (fetchXGetFTP(url, NULL, flags)); 97563340Sdes} 97663340Sdes 97763340Sdes/* 97841869Sdes * Put file 97941869Sdes */ 98037608SdesFILE * 98175891SarchiefetchPutFTP(struct url *url, const char *flags) 98237535Sdes{ 98341869Sdes 98490267Sdes return _ftp_request(url, CHECK_FLAG('a') ? "APPE" : "STOR", NULL, 98590267Sdes _ftp_get_proxy(), flags); 98637535Sdes} 98740975Sdes 98841869Sdes/* 98941869Sdes * Get file stats 99041869Sdes */ 99140975Sdesint 99275891SarchiefetchStatFTP(struct url *url, struct url_stat *us, const char *flags) 99340975Sdes{ 99490267Sdes 99590267Sdes if (_ftp_request(url, "STAT", us, _ftp_get_proxy(), flags) == NULL) 99690267Sdes return (-1); 99790267Sdes return (0); 99840975Sdes} 99941989Sdes 100041989Sdes/* 100141989Sdes * List a directory 100241989Sdes */ 100341989Sdesstruct url_ent * 100485093SdesfetchListFTP(struct url *url __unused, const char *flags __unused) 100541989Sdes{ 100690267Sdes warnx("fetchListFTP(): not implemented"); 100790267Sdes return (NULL); 100841989Sdes} 1009