ftp.c revision 99253
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 99253 2002-07-02 11:09:02Z ume $"); 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 { 31497866Sdes conn_t *cconn; /* Control connection */ 31597866Sdes conn_t *dconn; /* Data connection */ 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 } 33797866Sdes if (io->cconn == NULL || io->dconn == NULL || 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); 34797866Sdes r = _fetch_read(io->dconn, 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 } 37097866Sdes if (io->cconn == NULL || io->dconn == NULL || io->dir == O_RDONLY) { 37190267Sdes errno = EBADF; 37290267Sdes return (-1); 37390267Sdes } 37490267Sdes if (io->err) { 37590267Sdes errno = io->err; 37690267Sdes return (-1); 37790267Sdes } 37897866Sdes w = _fetch_write(io->dconn, 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); 41397866Sdes if (io->cconn == NULL || io->dconn == NULL) { 41490267Sdes errno = EBADF; 41590267Sdes return (-1); 41690267Sdes } 41797866Sdes _fetch_close(io->dconn); 41890267Sdes io->dir = -1; 41997866Sdes io->dconn = NULL; 42090267Sdes DEBUG(fprintf(stderr, "Waiting for final status\n")); 42197866Sdes r = _ftp_chkerr(io->cconn); 42297866Sdes _fetch_close(io->cconn); 42390267Sdes free(io); 42490267Sdes return (r == FTP_TRANSFER_COMPLETE) ? 0 : -1; 42567430Sdes} 42667430Sdes 42767430Sdesstatic FILE * 42897866Sdes_ftp_setup(conn_t *cconn, conn_t *dconn, int mode) 42967430Sdes{ 43090267Sdes struct ftpio *io; 43190267Sdes FILE *f; 43267430Sdes 43397866Sdes if (cconn == NULL || dconn == NULL) 43497866Sdes return (NULL); 43590267Sdes if ((io = malloc(sizeof *io)) == NULL) 43690267Sdes return (NULL); 43797866Sdes io->cconn = cconn; 43897866Sdes io->dconn = dconn; 43990267Sdes io->dir = mode; 44090267Sdes io->eof = io->err = 0; 44190267Sdes f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn); 44290267Sdes if (f == NULL) 44390267Sdes free(io); 44490267Sdes return (f); 44567430Sdes} 44667430Sdes 44767430Sdes/* 44837608Sdes * Transfer file 44937535Sdes */ 45037535Sdesstatic FILE * 45197856Sdes_ftp_transfer(conn_t *conn, const char *oper, const char *file, 45290267Sdes int mode, off_t offset, const char *flags) 45337535Sdes{ 45490267Sdes struct sockaddr_storage sa; 45590267Sdes struct sockaddr_in6 *sin6; 45690267Sdes struct sockaddr_in *sin4; 45790267Sdes int low, pasv, verbose; 45890267Sdes int e, sd = -1; 45990267Sdes socklen_t l; 46090267Sdes char *s; 46190267Sdes FILE *df; 46255544Sdes 46390267Sdes /* check flags */ 46490267Sdes low = CHECK_FLAG('l'); 46590267Sdes pasv = CHECK_FLAG('p'); 46690267Sdes verbose = CHECK_FLAG('v'); 46755544Sdes 46890267Sdes /* passive mode */ 46990267Sdes if (!pasv) 47090267Sdes pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL && 47190267Sdes strncasecmp(s, "no", 2) != 0); 47260951Sdes 47390267Sdes /* find our own address, bind, and listen */ 47490267Sdes l = sizeof sa; 47597856Sdes if (getsockname(conn->sd, (struct sockaddr *)&sa, &l) == -1) 47690267Sdes goto sysouch; 47790267Sdes if (sa.ss_family == AF_INET6) 47890267Sdes unmappedaddr((struct sockaddr_in6 *)&sa); 47960737Sume 48090267Sdes /* open data socket */ 48190267Sdes if ((sd = socket(sa.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 48290267Sdes _fetch_syserr(); 48390267Sdes return (NULL); 48460737Sume } 48537573Sdes 48690267Sdes if (pasv) { 48790267Sdes u_char addr[64]; 48890267Sdes char *ln, *p; 48990267Sdes unsigned int i; 49090267Sdes int port; 49137573Sdes 49290267Sdes /* send PASV command */ 49390267Sdes if (verbose) 49490267Sdes _fetch_info("setting passive mode"); 49590267Sdes switch (sa.ss_family) { 49690267Sdes case AF_INET: 49797856Sdes if ((e = _ftp_cmd(conn, "PASV")) != FTP_PASSIVE_MODE) 49890267Sdes goto ouch; 49990267Sdes break; 50090267Sdes case AF_INET6: 50197856Sdes if ((e = _ftp_cmd(conn, "EPSV")) != FTP_EPASSIVE_MODE) { 50290267Sdes if (e == -1) 50390267Sdes goto ouch; 50497856Sdes if ((e = _ftp_cmd(conn, "LPSV")) != 50597856Sdes FTP_LPASSIVE_MODE) 50690267Sdes goto ouch; 50790267Sdes } 50890267Sdes break; 50990267Sdes default: 51090267Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 51190267Sdes goto ouch; 51290267Sdes } 51337573Sdes 51490267Sdes /* 51590267Sdes * Find address and port number. The reply to the PASV command 51690267Sdes * is IMHO the one and only weak point in the FTP protocol. 51790267Sdes */ 51897856Sdes ln = conn->buf; 51990267Sdes switch (e) { 52090267Sdes case FTP_PASSIVE_MODE: 52190267Sdes case FTP_LPASSIVE_MODE: 52290267Sdes for (p = ln + 3; *p && !isdigit(*p); p++) 52390267Sdes /* nothing */ ; 52490267Sdes if (!*p) { 52590267Sdes e = FTP_PROTOCOL_ERROR; 52690267Sdes goto ouch; 52790267Sdes } 52890267Sdes l = (e == FTP_PASSIVE_MODE ? 6 : 21); 52990267Sdes for (i = 0; *p && i < l; i++, p++) 53090267Sdes addr[i] = strtol(p, &p, 10); 53190267Sdes if (i < l) { 53290267Sdes e = FTP_PROTOCOL_ERROR; 53390267Sdes goto ouch; 53490267Sdes } 53590267Sdes break; 53690267Sdes case FTP_EPASSIVE_MODE: 53790267Sdes for (p = ln + 3; *p && *p != '('; p++) 53890267Sdes /* nothing */ ; 53990267Sdes if (!*p) { 54090267Sdes e = FTP_PROTOCOL_ERROR; 54190267Sdes goto ouch; 54290267Sdes } 54390267Sdes ++p; 54490267Sdes if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 54590267Sdes &port, &addr[3]) != 5 || 54690267Sdes addr[0] != addr[1] || 54790267Sdes addr[0] != addr[2] || addr[0] != addr[3]) { 54890267Sdes e = FTP_PROTOCOL_ERROR; 54990267Sdes goto ouch; 55090267Sdes } 55190267Sdes break; 55290267Sdes } 55360188Sdes 55490267Sdes /* seek to required offset */ 55590267Sdes if (offset) 55697856Sdes if (_ftp_cmd(conn, "REST %lu", (u_long)offset) != FTP_FILE_OK) 55790267Sdes goto sysouch; 55890267Sdes 55990267Sdes /* construct sockaddr for data socket */ 56090267Sdes l = sizeof sa; 56197856Sdes if (getpeername(conn->sd, (struct sockaddr *)&sa, &l) == -1) 56290267Sdes goto sysouch; 56390267Sdes if (sa.ss_family == AF_INET6) 56490267Sdes unmappedaddr((struct sockaddr_in6 *)&sa); 56590267Sdes switch (sa.ss_family) { 56690267Sdes case AF_INET6: 56790267Sdes sin6 = (struct sockaddr_in6 *)&sa; 56890267Sdes if (e == FTP_EPASSIVE_MODE) 56990267Sdes sin6->sin6_port = htons(port); 57090267Sdes else { 57190267Sdes bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 57290267Sdes bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 57390267Sdes } 57490267Sdes break; 57590267Sdes case AF_INET: 57690267Sdes sin4 = (struct sockaddr_in *)&sa; 57790267Sdes if (e == FTP_EPASSIVE_MODE) 57890267Sdes sin4->sin_port = htons(port); 57990267Sdes else { 58090267Sdes bcopy(addr, (char *)&sin4->sin_addr, 4); 58190267Sdes bcopy(addr + 4, (char *)&sin4->sin_port, 2); 58290267Sdes } 58390267Sdes break; 58490267Sdes default: 58590267Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 58690267Sdes break; 58790267Sdes } 58890267Sdes 58990267Sdes /* connect to data port */ 59090267Sdes if (verbose) 59190267Sdes _fetch_info("opening data connection"); 59290267Sdes if (connect(sd, (struct sockaddr *)&sa, sa.ss_len) == -1) 59390267Sdes goto sysouch; 59490267Sdes 59590267Sdes /* make the server initiate the transfer */ 59690267Sdes if (verbose) 59790267Sdes _fetch_info("initiating transfer"); 59897856Sdes e = _ftp_cmd(conn, "%s %s", oper, _ftp_filename(file)); 59990267Sdes if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) 60090267Sdes goto ouch; 60190267Sdes 60290267Sdes } else { 60390267Sdes u_int32_t a; 60490267Sdes u_short p; 60590267Sdes int arg, d; 60690267Sdes char *ap; 60790267Sdes char hname[INET6_ADDRSTRLEN]; 60890267Sdes 60990267Sdes switch (sa.ss_family) { 61090267Sdes case AF_INET6: 61190267Sdes ((struct sockaddr_in6 *)&sa)->sin6_port = 0; 61260737Sume#ifdef IPV6_PORTRANGE 61390267Sdes arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH; 61490267Sdes if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 61590267Sdes (char *)&arg, sizeof(arg)) == -1) 61690267Sdes goto sysouch; 61760737Sume#endif 61890267Sdes break; 61990267Sdes case AF_INET: 62090267Sdes ((struct sockaddr_in *)&sa)->sin_port = 0; 62190267Sdes arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH; 62290267Sdes if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 62390267Sdes (char *)&arg, sizeof arg) == -1) 62490267Sdes goto sysouch; 62590267Sdes break; 62690267Sdes } 62790267Sdes if (verbose) 62890267Sdes _fetch_info("binding data socket"); 62990267Sdes if (bind(sd, (struct sockaddr *)&sa, sa.ss_len) == -1) 63090267Sdes goto sysouch; 63190267Sdes if (listen(sd, 1) == -1) 63290267Sdes goto sysouch; 63337573Sdes 63490267Sdes /* find what port we're on and tell the server */ 63590267Sdes if (getsockname(sd, (struct sockaddr *)&sa, &l) == -1) 63690267Sdes goto sysouch; 63790267Sdes switch (sa.ss_family) { 63890267Sdes case AF_INET: 63990267Sdes sin4 = (struct sockaddr_in *)&sa; 64090267Sdes a = ntohl(sin4->sin_addr.s_addr); 64190267Sdes p = ntohs(sin4->sin_port); 64297856Sdes e = _ftp_cmd(conn, "PORT %d,%d,%d,%d,%d,%d", 64390267Sdes (a >> 24) & 0xff, (a >> 16) & 0xff, 64490267Sdes (a >> 8) & 0xff, a & 0xff, 64590267Sdes (p >> 8) & 0xff, p & 0xff); 64690267Sdes break; 64790267Sdes case AF_INET6: 64860737Sume#define UC(b) (((int)b)&0xff) 64990267Sdes e = -1; 65090267Sdes sin6 = (struct sockaddr_in6 *)&sa; 65199253Sume sin6->sin6_scope_id = 0; 65290267Sdes if (getnameinfo((struct sockaddr *)&sa, sa.ss_len, 65390267Sdes hname, sizeof(hname), 65490267Sdes NULL, 0, NI_NUMERICHOST) == 0) { 65597856Sdes e = _ftp_cmd(conn, "EPRT |%d|%s|%d|", 2, hname, 65690267Sdes htons(sin6->sin6_port)); 65790267Sdes if (e == -1) 65890267Sdes goto ouch; 65990267Sdes } 66090267Sdes if (e != FTP_OK) { 66190267Sdes ap = (char *)&sin6->sin6_addr; 66297856Sdes e = _ftp_cmd(conn, 66390267Sdes "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 66490267Sdes 6, 16, 66590267Sdes UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 66690267Sdes UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 66790267Sdes UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 66890267Sdes UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 66990267Sdes 2, 67090267Sdes (ntohs(sin6->sin6_port) >> 8) & 0xff, 67190267Sdes ntohs(sin6->sin6_port) & 0xff); 67290267Sdes } 67390267Sdes break; 67490267Sdes default: 67590267Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 67690267Sdes goto ouch; 67790267Sdes } 67890267Sdes if (e != FTP_OK) 67990267Sdes goto ouch; 68090267Sdes 68190267Sdes /* seek to required offset */ 68290267Sdes if (offset) 68397856Sdes if (_ftp_cmd(conn, "REST %ju", (uintmax_t)offset) != FTP_FILE_OK) 68490267Sdes goto sysouch; 68590267Sdes 68690267Sdes /* make the server initiate the transfer */ 68790267Sdes if (verbose) 68890267Sdes _fetch_info("initiating transfer"); 68997856Sdes e = _ftp_cmd(conn, "%s %s", oper, _ftp_filename(file)); 69090267Sdes if (e != FTP_OPEN_DATA_CONNECTION) 69190267Sdes goto ouch; 69290267Sdes 69390267Sdes /* accept the incoming connection and go to town */ 69490267Sdes if ((d = accept(sd, NULL, NULL)) == -1) 69590267Sdes goto sysouch; 69690267Sdes close(sd); 69790267Sdes sd = d; 69860737Sume } 69937573Sdes 70097866Sdes if ((df = _ftp_setup(conn, _fetch_reopen(sd), mode)) == NULL) 70162256Sdes goto sysouch; 70290267Sdes return (df); 70337573Sdes 70437573Sdessysouch: 70590267Sdes _fetch_syserr(); 70690267Sdes if (sd >= 0) 70790267Sdes close(sd); 70890267Sdes return (NULL); 70941869Sdes 71037573Sdesouch: 71190267Sdes if (e != -1) 71290267Sdes _ftp_seterr(e); 71390267Sdes if (sd >= 0) 71490267Sdes close(sd); 71590267Sdes return (NULL); 71637535Sdes} 71737535Sdes 71837571Sdes/* 71977238Sdes * Authenticate 72077238Sdes */ 72177238Sdesstatic int 72297856Sdes_ftp_authenticate(conn_t *conn, struct url *url, struct url *purl) 72377238Sdes{ 72490267Sdes const char *user, *pwd, *logname; 72590267Sdes char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 72690267Sdes int e, len; 72777238Sdes 72890267Sdes /* XXX FTP_AUTH, and maybe .netrc */ 72990267Sdes 73090267Sdes /* send user name and password */ 73190267Sdes user = url->user; 73290267Sdes if (!user || !*user) 73390267Sdes user = getenv("FTP_LOGIN"); 73490267Sdes if (!user || !*user) 73590267Sdes user = FTP_ANONYMOUS_USER; 73690267Sdes if (purl && url->port == _fetch_default_port(url->scheme)) 73797856Sdes e = _ftp_cmd(conn, "USER %s@%s", user, url->host); 73890267Sdes else if (purl) 73997856Sdes e = _ftp_cmd(conn, "USER %s@%s@%d", user, url->host, url->port); 74090267Sdes else 74197856Sdes e = _ftp_cmd(conn, "USER %s", user); 74290267Sdes 74390267Sdes /* did the server request a password? */ 74490267Sdes if (e == FTP_NEED_PASSWORD) { 74590267Sdes pwd = url->pwd; 74690267Sdes if (!pwd || !*pwd) 74790267Sdes pwd = getenv("FTP_PASSWORD"); 74890267Sdes if (!pwd || !*pwd) { 74990267Sdes if ((logname = getlogin()) == 0) 75090267Sdes logname = FTP_ANONYMOUS_USER; 75190267Sdes if ((len = snprintf(pbuf, MAXLOGNAME + 1, "%s@", logname)) < 0) 75290267Sdes len = 0; 75390267Sdes else if (len > MAXLOGNAME) 75490267Sdes len = MAXLOGNAME; 75590267Sdes gethostname(pbuf + len, sizeof pbuf - len); 75690267Sdes pwd = pbuf; 75790267Sdes } 75897856Sdes e = _ftp_cmd(conn, "PASS %s", pwd); 75977238Sdes } 76090267Sdes 76190267Sdes return (e); 76277238Sdes} 76377238Sdes 76477238Sdes/* 76537571Sdes * Log on to FTP server 76637535Sdes */ 76797856Sdesstatic conn_t * 76875891Sarchie_ftp_connect(struct url *url, struct url *purl, const char *flags) 76937571Sdes{ 77097856Sdes conn_t *conn; 77197856Sdes int 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! */ 79197856Sdes conn = _fetch_connect(purl->host, purl->port, af, verbose); 79290267Sdes } else { 79390267Sdes /* no proxy, go straight to target */ 79497856Sdes conn = _fetch_connect(url->host, url->port, af, verbose); 79590267Sdes purl = NULL; 79690267Sdes } 79737608Sdes 79890267Sdes /* check connection */ 79997856Sdes if (conn == NULL) { 80090267Sdes _fetch_syserr(); 80190267Sdes return (NULL); 80290267Sdes } 80390267Sdes 80490267Sdes /* expect welcome message */ 80597856Sdes if ((e = _ftp_chkerr(conn)) != FTP_SERVICE_READY) 80690267Sdes goto fouch; 80790267Sdes 80890267Sdes /* authenticate */ 80997856Sdes if ((e = _ftp_authenticate(conn, 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 81497856Sdes if ((e = _ftp_cmd(conn, "MODE S")) != FTP_OK) /* default is S */ 81590267Sdes goto fouch; 81637571Sdes#endif 81797856Sdes if ((e = _ftp_cmd(conn, "TYPE I")) != FTP_OK) /* default is A */ 81890267Sdes goto fouch; 81937571Sdes 82090267Sdes /* done */ 82197856Sdes return (conn); 82290267Sdes 82337571Sdesfouch: 82490267Sdes if (e != -1) 82590267Sdes _ftp_seterr(e); 82697856Sdes _fetch_close(conn); 82790267Sdes return (NULL); 82837571Sdes} 82937571Sdes 83037571Sdes/* 83137571Sdes * Disconnect from server 83237571Sdes */ 83337571Sdesstatic void 83497856Sdes_ftp_disconnect(conn_t *conn) 83537571Sdes{ 83697856Sdes (void)_ftp_cmd(conn, "QUIT"); 83797856Sdes _fetch_close(conn); 83837571Sdes} 83937571Sdes 84037571Sdes/* 84137571Sdes * Check if we're already connected 84237571Sdes */ 84337571Sdesstatic int 84440975Sdes_ftp_isconnected(struct url *url) 84537571Sdes{ 84697856Sdes return (cached_connection 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 */ 85697856Sdesstatic conn_t * 85775891Sarchie_ftp_cached_connect(struct url *url, struct url *purl, const char *flags) 85837535Sdes{ 85997856Sdes conn_t *conn; 86097856Sdes int e; 86137535Sdes 86290267Sdes /* set default port */ 86390267Sdes if (!url->port) 86490267Sdes url->port = _fetch_default_port(url->scheme); 86590267Sdes 86690267Sdes /* try to use previously cached connection */ 86790267Sdes if (_ftp_isconnected(url)) { 86897856Sdes e = _ftp_cmd(cached_connection, "NOOP"); 86990267Sdes if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 87097856Sdes return (cached_connection); 87190267Sdes } 87290267Sdes 87390267Sdes /* connect to server */ 87497856Sdes if ((conn = _ftp_connect(url, purl, flags)) == NULL) 87597856Sdes return (NULL); 87697856Sdes if (cached_connection) 87797856Sdes _ftp_disconnect(cached_connection); 87898117Sdes cached_connection = _fetch_ref(conn); 87990267Sdes memcpy(&cached_host, url, sizeof *url); 88097856Sdes return (conn); 88137535Sdes} 88237535Sdes 88337571Sdes/* 88467043Sdes * Check the proxy settings 88563713Sdes */ 88667043Sdesstatic struct url * 88767043Sdes_ftp_get_proxy(void) 88863713Sdes{ 88990267Sdes struct url *purl; 89090267Sdes char *p; 89190267Sdes 89290267Sdes if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) || 89390267Sdes (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) && 89490267Sdes *p && (purl = fetchParseURL(p)) != NULL) { 89590267Sdes if (!*purl->scheme) { 89690267Sdes if (getenv("FTP_PROXY") || getenv("ftp_proxy")) 89790267Sdes strcpy(purl->scheme, SCHEME_FTP); 89890267Sdes else 89990267Sdes strcpy(purl->scheme, SCHEME_HTTP); 90090267Sdes } 90190267Sdes if (!purl->port) 90290267Sdes purl->port = _fetch_default_proxy_port(purl->scheme); 90390267Sdes if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 90490267Sdes strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 90590267Sdes return (purl); 90690267Sdes fetchFreeURL(purl); 90769272Sdes } 90890267Sdes return (NULL); 90963713Sdes} 91063713Sdes 91163713Sdes/* 91287315Sdes * Process an FTP request 91337571Sdes */ 91437535SdesFILE * 91587315Sdes_ftp_request(struct url *url, const char *op, struct url_stat *us, 91690267Sdes struct url *purl, const char *flags) 91737608Sdes{ 91897856Sdes conn_t *conn; 91997856Sdes int 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 */ 93497856Sdes conn = _ftp_cached_connect(url, purl, flags); 93590267Sdes if (purl) 93690267Sdes fetchFreeURL(purl); 93797856Sdes if (conn == NULL) 93890267Sdes return (NULL); 93990267Sdes 94090267Sdes /* change directory */ 94197856Sdes if (_ftp_cwd(conn, url->doc) == -1) 94290267Sdes return (NULL); 94390267Sdes 94490267Sdes /* stat file */ 94597856Sdes if (us && _ftp_stat(conn, 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 */ 95997856Sdes return (_ftp_transfer(conn, 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