ftp.c revision 105903
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 105903 2002-10-25 01:17:32Z njl $"); 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); 422105903Snjl if (io->cconn == cached_connection && io->cconn->ref == 1) 423105903Snjl cached_connection = NULL; 42497866Sdes _fetch_close(io->cconn); 42590267Sdes free(io); 42690267Sdes return (r == FTP_TRANSFER_COMPLETE) ? 0 : -1; 42767430Sdes} 42867430Sdes 42967430Sdesstatic FILE * 43097866Sdes_ftp_setup(conn_t *cconn, conn_t *dconn, int mode) 43167430Sdes{ 43290267Sdes struct ftpio *io; 43390267Sdes FILE *f; 43467430Sdes 43597866Sdes if (cconn == NULL || dconn == NULL) 43697866Sdes return (NULL); 43790267Sdes if ((io = malloc(sizeof *io)) == NULL) 43890267Sdes return (NULL); 43997866Sdes io->cconn = cconn; 44097866Sdes io->dconn = dconn; 44190267Sdes io->dir = mode; 44290267Sdes io->eof = io->err = 0; 44390267Sdes f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn); 44490267Sdes if (f == NULL) 44590267Sdes free(io); 44690267Sdes return (f); 44767430Sdes} 44867430Sdes 44967430Sdes/* 45037608Sdes * Transfer file 45137535Sdes */ 45237535Sdesstatic FILE * 45397856Sdes_ftp_transfer(conn_t *conn, const char *oper, const char *file, 45490267Sdes int mode, off_t offset, const char *flags) 45537535Sdes{ 45690267Sdes struct sockaddr_storage sa; 45790267Sdes struct sockaddr_in6 *sin6; 45890267Sdes struct sockaddr_in *sin4; 45990267Sdes int low, pasv, verbose; 46090267Sdes int e, sd = -1; 46190267Sdes socklen_t l; 46290267Sdes char *s; 46390267Sdes FILE *df; 46455544Sdes 46590267Sdes /* check flags */ 46690267Sdes low = CHECK_FLAG('l'); 46790267Sdes pasv = CHECK_FLAG('p'); 46890267Sdes verbose = CHECK_FLAG('v'); 46955544Sdes 47090267Sdes /* passive mode */ 47190267Sdes if (!pasv) 47290267Sdes pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL && 47390267Sdes strncasecmp(s, "no", 2) != 0); 47460951Sdes 47590267Sdes /* find our own address, bind, and listen */ 47690267Sdes l = sizeof sa; 47797856Sdes if (getsockname(conn->sd, (struct sockaddr *)&sa, &l) == -1) 47890267Sdes goto sysouch; 47990267Sdes if (sa.ss_family == AF_INET6) 48090267Sdes unmappedaddr((struct sockaddr_in6 *)&sa); 48160737Sume 48290267Sdes /* open data socket */ 48390267Sdes if ((sd = socket(sa.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 48490267Sdes _fetch_syserr(); 48590267Sdes return (NULL); 48660737Sume } 48737573Sdes 48890267Sdes if (pasv) { 48990267Sdes u_char addr[64]; 49090267Sdes char *ln, *p; 49190267Sdes unsigned int i; 49290267Sdes int port; 49337573Sdes 49490267Sdes /* send PASV command */ 49590267Sdes if (verbose) 49690267Sdes _fetch_info("setting passive mode"); 49790267Sdes switch (sa.ss_family) { 49890267Sdes case AF_INET: 49997856Sdes if ((e = _ftp_cmd(conn, "PASV")) != FTP_PASSIVE_MODE) 50090267Sdes goto ouch; 50190267Sdes break; 50290267Sdes case AF_INET6: 50397856Sdes if ((e = _ftp_cmd(conn, "EPSV")) != FTP_EPASSIVE_MODE) { 50490267Sdes if (e == -1) 50590267Sdes goto ouch; 50697856Sdes if ((e = _ftp_cmd(conn, "LPSV")) != 50797856Sdes 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 */ 52097856Sdes ln = conn->buf; 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) 55897856Sdes if (_ftp_cmd(conn, "REST %lu", (u_long)offset) != FTP_FILE_OK) 55990267Sdes goto sysouch; 56090267Sdes 56190267Sdes /* construct sockaddr for data socket */ 56290267Sdes l = sizeof sa; 56397856Sdes if (getpeername(conn->sd, (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"); 60097856Sdes e = _ftp_cmd(conn, "%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); 64497856Sdes e = _ftp_cmd(conn, "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; 65399253Sume sin6->sin6_scope_id = 0; 65490267Sdes if (getnameinfo((struct sockaddr *)&sa, sa.ss_len, 65590267Sdes hname, sizeof(hname), 65690267Sdes NULL, 0, NI_NUMERICHOST) == 0) { 65797856Sdes e = _ftp_cmd(conn, "EPRT |%d|%s|%d|", 2, hname, 65890267Sdes htons(sin6->sin6_port)); 65990267Sdes if (e == -1) 66090267Sdes goto ouch; 66190267Sdes } 66290267Sdes if (e != FTP_OK) { 66390267Sdes ap = (char *)&sin6->sin6_addr; 66497856Sdes e = _ftp_cmd(conn, 66590267Sdes "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 66690267Sdes 6, 16, 66790267Sdes UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 66890267Sdes UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 66990267Sdes UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 67090267Sdes UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 67190267Sdes 2, 67290267Sdes (ntohs(sin6->sin6_port) >> 8) & 0xff, 67390267Sdes ntohs(sin6->sin6_port) & 0xff); 67490267Sdes } 67590267Sdes break; 67690267Sdes default: 67790267Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 67890267Sdes goto ouch; 67990267Sdes } 68090267Sdes if (e != FTP_OK) 68190267Sdes goto ouch; 68290267Sdes 68390267Sdes /* seek to required offset */ 68490267Sdes if (offset) 68597856Sdes if (_ftp_cmd(conn, "REST %ju", (uintmax_t)offset) != FTP_FILE_OK) 68690267Sdes goto sysouch; 68790267Sdes 68890267Sdes /* make the server initiate the transfer */ 68990267Sdes if (verbose) 69090267Sdes _fetch_info("initiating transfer"); 69197856Sdes e = _ftp_cmd(conn, "%s %s", oper, _ftp_filename(file)); 69290267Sdes if (e != FTP_OPEN_DATA_CONNECTION) 69390267Sdes goto ouch; 69490267Sdes 69590267Sdes /* accept the incoming connection and go to town */ 69690267Sdes if ((d = accept(sd, NULL, NULL)) == -1) 69790267Sdes goto sysouch; 69890267Sdes close(sd); 69990267Sdes sd = d; 70060737Sume } 70137573Sdes 70297866Sdes if ((df = _ftp_setup(conn, _fetch_reopen(sd), mode)) == NULL) 70362256Sdes goto sysouch; 70490267Sdes return (df); 70537573Sdes 70637573Sdessysouch: 70790267Sdes _fetch_syserr(); 70890267Sdes if (sd >= 0) 70990267Sdes close(sd); 71090267Sdes return (NULL); 71141869Sdes 71237573Sdesouch: 71390267Sdes if (e != -1) 71490267Sdes _ftp_seterr(e); 71590267Sdes if (sd >= 0) 71690267Sdes close(sd); 71790267Sdes return (NULL); 71837535Sdes} 71937535Sdes 72037571Sdes/* 72177238Sdes * Authenticate 72277238Sdes */ 72377238Sdesstatic int 72497856Sdes_ftp_authenticate(conn_t *conn, struct url *url, struct url *purl) 72577238Sdes{ 72690267Sdes const char *user, *pwd, *logname; 72790267Sdes char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 72890267Sdes int e, len; 72977238Sdes 73090267Sdes /* XXX FTP_AUTH, and maybe .netrc */ 73190267Sdes 73290267Sdes /* send user name and password */ 73390267Sdes user = url->user; 73490267Sdes if (!user || !*user) 73590267Sdes user = getenv("FTP_LOGIN"); 73690267Sdes if (!user || !*user) 73790267Sdes user = FTP_ANONYMOUS_USER; 73890267Sdes if (purl && url->port == _fetch_default_port(url->scheme)) 73997856Sdes e = _ftp_cmd(conn, "USER %s@%s", user, url->host); 74090267Sdes else if (purl) 74197856Sdes e = _ftp_cmd(conn, "USER %s@%s@%d", user, url->host, url->port); 74290267Sdes else 74397856Sdes e = _ftp_cmd(conn, "USER %s", user); 74490267Sdes 74590267Sdes /* did the server request a password? */ 74690267Sdes if (e == FTP_NEED_PASSWORD) { 74790267Sdes pwd = url->pwd; 74890267Sdes if (!pwd || !*pwd) 74990267Sdes pwd = getenv("FTP_PASSWORD"); 75090267Sdes if (!pwd || !*pwd) { 75190267Sdes if ((logname = getlogin()) == 0) 75290267Sdes logname = FTP_ANONYMOUS_USER; 75390267Sdes if ((len = snprintf(pbuf, MAXLOGNAME + 1, "%s@", logname)) < 0) 75490267Sdes len = 0; 75590267Sdes else if (len > MAXLOGNAME) 75690267Sdes len = MAXLOGNAME; 75790267Sdes gethostname(pbuf + len, sizeof pbuf - len); 75890267Sdes pwd = pbuf; 75990267Sdes } 76097856Sdes e = _ftp_cmd(conn, "PASS %s", pwd); 76177238Sdes } 76290267Sdes 76390267Sdes return (e); 76477238Sdes} 76577238Sdes 76677238Sdes/* 76737571Sdes * Log on to FTP server 76837535Sdes */ 76997856Sdesstatic conn_t * 77075891Sarchie_ftp_connect(struct url *url, struct url *purl, const char *flags) 77137571Sdes{ 77297856Sdes conn_t *conn; 77397856Sdes int e, direct, verbose; 77460737Sume#ifdef INET6 77590267Sdes int af = AF_UNSPEC; 77660737Sume#else 77790267Sdes int af = AF_INET; 77860737Sume#endif 77937571Sdes 78090267Sdes direct = CHECK_FLAG('d'); 78190267Sdes verbose = CHECK_FLAG('v'); 78290267Sdes if (CHECK_FLAG('4')) 78390267Sdes af = AF_INET; 78490267Sdes else if (CHECK_FLAG('6')) 78590267Sdes af = AF_INET6; 78660737Sume 78790267Sdes if (direct) 78890267Sdes purl = NULL; 78937608Sdes 79090267Sdes /* check for proxy */ 79190267Sdes if (purl) { 79290267Sdes /* XXX proxy authentication! */ 79397856Sdes conn = _fetch_connect(purl->host, purl->port, af, verbose); 79490267Sdes } else { 79590267Sdes /* no proxy, go straight to target */ 79697856Sdes conn = _fetch_connect(url->host, url->port, af, verbose); 79790267Sdes purl = NULL; 79890267Sdes } 79937608Sdes 80090267Sdes /* check connection */ 801103459Sfenner if (conn == NULL) 802103459Sfenner /* _fetch_connect() has already set an error code */ 80390267Sdes return (NULL); 80490267Sdes 80590267Sdes /* expect welcome message */ 80697856Sdes if ((e = _ftp_chkerr(conn)) != FTP_SERVICE_READY) 80790267Sdes goto fouch; 80890267Sdes 80990267Sdes /* authenticate */ 81097856Sdes if ((e = _ftp_authenticate(conn, url, purl)) != FTP_LOGGED_IN) 81190267Sdes goto fouch; 81290267Sdes 81390267Sdes /* might as well select mode and type at once */ 81437571Sdes#ifdef FTP_FORCE_STREAM_MODE 81597856Sdes if ((e = _ftp_cmd(conn, "MODE S")) != FTP_OK) /* default is S */ 81690267Sdes goto fouch; 81737571Sdes#endif 81897856Sdes if ((e = _ftp_cmd(conn, "TYPE I")) != FTP_OK) /* default is A */ 81990267Sdes goto fouch; 82037571Sdes 82190267Sdes /* done */ 82297856Sdes return (conn); 82390267Sdes 82437571Sdesfouch: 82590267Sdes if (e != -1) 82690267Sdes _ftp_seterr(e); 82797856Sdes _fetch_close(conn); 82890267Sdes return (NULL); 82937571Sdes} 83037571Sdes 83137571Sdes/* 83237571Sdes * Disconnect from server 83337571Sdes */ 83437571Sdesstatic void 83597856Sdes_ftp_disconnect(conn_t *conn) 83637571Sdes{ 83797856Sdes (void)_ftp_cmd(conn, "QUIT"); 838105903Snjl if (conn == cached_connection && conn->ref == 1) 839105903Snjl cached_connection = NULL; 84097856Sdes _fetch_close(conn); 84137571Sdes} 84237571Sdes 84337571Sdes/* 84437571Sdes * Check if we're already connected 84537571Sdes */ 84637571Sdesstatic int 84740975Sdes_ftp_isconnected(struct url *url) 84837571Sdes{ 84997856Sdes return (cached_connection 85037571Sdes && (strcmp(url->host, cached_host.host) == 0) 85137571Sdes && (strcmp(url->user, cached_host.user) == 0) 85237571Sdes && (strcmp(url->pwd, cached_host.pwd) == 0) 85337571Sdes && (url->port == cached_host.port)); 85437571Sdes} 85537571Sdes 85637608Sdes/* 85741869Sdes * Check the cache, reconnect if no luck 85837608Sdes */ 85997856Sdesstatic conn_t * 86075891Sarchie_ftp_cached_connect(struct url *url, struct url *purl, const char *flags) 86137535Sdes{ 86297856Sdes conn_t *conn; 86397856Sdes int e; 86437535Sdes 86590267Sdes /* set default port */ 86690267Sdes if (!url->port) 86790267Sdes url->port = _fetch_default_port(url->scheme); 86890267Sdes 86990267Sdes /* try to use previously cached connection */ 87090267Sdes if (_ftp_isconnected(url)) { 87197856Sdes e = _ftp_cmd(cached_connection, "NOOP"); 87290267Sdes if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 873105511Stjr return (_fetch_ref(cached_connection)); 87490267Sdes } 87590267Sdes 87690267Sdes /* connect to server */ 87797856Sdes if ((conn = _ftp_connect(url, purl, flags)) == NULL) 87897856Sdes return (NULL); 87997856Sdes if (cached_connection) 88097856Sdes _ftp_disconnect(cached_connection); 88198117Sdes cached_connection = _fetch_ref(conn); 88290267Sdes memcpy(&cached_host, url, sizeof *url); 88397856Sdes return (conn); 88437535Sdes} 88537535Sdes 88637571Sdes/* 88767043Sdes * Check the proxy settings 88863713Sdes */ 88967043Sdesstatic struct url * 89067043Sdes_ftp_get_proxy(void) 89163713Sdes{ 89290267Sdes struct url *purl; 89390267Sdes char *p; 89490267Sdes 89590267Sdes if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) || 89690267Sdes (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) && 89790267Sdes *p && (purl = fetchParseURL(p)) != NULL) { 89890267Sdes if (!*purl->scheme) { 89990267Sdes if (getenv("FTP_PROXY") || getenv("ftp_proxy")) 90090267Sdes strcpy(purl->scheme, SCHEME_FTP); 90190267Sdes else 90290267Sdes strcpy(purl->scheme, SCHEME_HTTP); 90390267Sdes } 90490267Sdes if (!purl->port) 90590267Sdes purl->port = _fetch_default_proxy_port(purl->scheme); 90690267Sdes if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 90790267Sdes strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 90890267Sdes return (purl); 90990267Sdes fetchFreeURL(purl); 91069272Sdes } 91190267Sdes return (NULL); 91263713Sdes} 91363713Sdes 91463713Sdes/* 91587315Sdes * Process an FTP request 91637571Sdes */ 91737535SdesFILE * 91887315Sdes_ftp_request(struct url *url, const char *op, struct url_stat *us, 91990267Sdes struct url *purl, const char *flags) 92037608Sdes{ 92197856Sdes conn_t *conn; 92297856Sdes int oflag; 92390267Sdes 92490267Sdes /* check if we should use HTTP instead */ 92590267Sdes if (purl && strcasecmp(purl->scheme, SCHEME_HTTP) == 0) { 92690267Sdes if (strcmp(op, "STAT") == 0) 92790267Sdes return (_http_request(url, "HEAD", us, purl, flags)); 92890267Sdes else if (strcmp(op, "RETR") == 0) 92990267Sdes return (_http_request(url, "GET", us, purl, flags)); 93090267Sdes /* 93190267Sdes * Our HTTP code doesn't support PUT requests yet, so try 93290267Sdes * a direct connection. 93390267Sdes */ 93490267Sdes } 93590267Sdes 93690267Sdes /* connect to server */ 93797856Sdes conn = _ftp_cached_connect(url, purl, flags); 93890267Sdes if (purl) 93990267Sdes fetchFreeURL(purl); 94097856Sdes if (conn == NULL) 94190267Sdes return (NULL); 94290267Sdes 94390267Sdes /* change directory */ 94497856Sdes if (_ftp_cwd(conn, url->doc) == -1) 94590267Sdes return (NULL); 94690267Sdes 94790267Sdes /* stat file */ 94897856Sdes if (us && _ftp_stat(conn, url->doc, us) == -1 94990267Sdes && fetchLastErrCode != FETCH_PROTO 95090267Sdes && fetchLastErrCode != FETCH_UNAVAIL) 95190267Sdes return (NULL); 95290267Sdes 95390267Sdes /* just a stat */ 95487315Sdes if (strcmp(op, "STAT") == 0) 95590267Sdes return (FILE *)1; /* bogus return value */ 95690267Sdes if (strcmp(op, "STOR") == 0 || strcmp(op, "APPE") == 0) 95790267Sdes oflag = O_WRONLY; 95890267Sdes else 95990267Sdes oflag = O_RDONLY; 96087315Sdes 96190267Sdes /* initiate the transfer */ 96297856Sdes return (_ftp_transfer(conn, op, url->doc, oflag, url->offset, flags)); 96337608Sdes} 96437608Sdes 96541869Sdes/* 96687315Sdes * Get and stat file 96787315Sdes */ 96887315SdesFILE * 96987315SdesfetchXGetFTP(struct url *url, struct url_stat *us, const char *flags) 97087315Sdes{ 97190267Sdes return (_ftp_request(url, "RETR", us, _ftp_get_proxy(), flags)); 97287315Sdes} 97387315Sdes 97487315Sdes/* 97563340Sdes * Get file 97663340Sdes */ 97763340SdesFILE * 97875891SarchiefetchGetFTP(struct url *url, const char *flags) 97963340Sdes{ 98090267Sdes return (fetchXGetFTP(url, NULL, flags)); 98163340Sdes} 98263340Sdes 98363340Sdes/* 98441869Sdes * Put file 98541869Sdes */ 98637608SdesFILE * 98775891SarchiefetchPutFTP(struct url *url, const char *flags) 98837535Sdes{ 98941869Sdes 99090267Sdes return _ftp_request(url, CHECK_FLAG('a') ? "APPE" : "STOR", NULL, 99190267Sdes _ftp_get_proxy(), flags); 99237535Sdes} 99340975Sdes 99441869Sdes/* 99541869Sdes * Get file stats 99641869Sdes */ 99740975Sdesint 99875891SarchiefetchStatFTP(struct url *url, struct url_stat *us, const char *flags) 99940975Sdes{ 100090267Sdes 100190267Sdes if (_ftp_request(url, "STAT", us, _ftp_get_proxy(), flags) == NULL) 100290267Sdes return (-1); 100390267Sdes return (0); 100440975Sdes} 100541989Sdes 100641989Sdes/* 100741989Sdes * List a directory 100841989Sdes */ 100941989Sdesstruct url_ent * 101085093SdesfetchListFTP(struct url *url __unused, const char *flags __unused) 101141989Sdes{ 101290267Sdes warnx("fetchListFTP(): not implemented"); 101390267Sdes return (NULL); 101441989Sdes} 1015