ftp.c revision 85093
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 85093 2001-10-18 08:29:26Z 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): 3737535Sdes * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you 3837535Sdes * can do whatever you want with this stuff. If we meet some day, and you think 3937535Sdes * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 4037535Sdes * ---------------------------------------------------------------------------- 4137535Sdes * 4237535Sdes * Major Changelog: 4337535Sdes * 4437535Sdes * Dag-Erling Co�dan Sm�rgrav 4537535Sdes * 9 Jun 1998 4637535Sdes * 4737535Sdes * Incorporated into libfetch 4837535Sdes * 4937535Sdes * Jordan K. Hubbard 5037535Sdes * 17 Jan 1996 5137535Sdes * 5237535Sdes * Turned inside out. Now returns xfers as new file ids, not as a special 5337535Sdes * `state' of FTP_t 5437535Sdes * 5537535Sdes * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $ 5637535Sdes * 5737535Sdes */ 5837535Sdes 5941862Sdes#include <sys/param.h> 6037535Sdes#include <sys/socket.h> 6137535Sdes#include <netinet/in.h> 6237535Sdes 6337535Sdes#include <ctype.h> 6475891Sarchie#include <err.h> 6555557Sdes#include <errno.h> 6667430Sdes#include <fcntl.h> 6760188Sdes#include <netdb.h> 6837573Sdes#include <stdarg.h> 6937535Sdes#include <stdio.h> 7037571Sdes#include <stdlib.h> 7137535Sdes#include <string.h> 7241869Sdes#include <time.h> 7337571Sdes#include <unistd.h> 7437535Sdes 7537535Sdes#include "fetch.h" 7640939Sdes#include "common.h" 7741862Sdes#include "ftperr.h" 7837535Sdes 7970795Sdes#define FTP_ANONYMOUS_USER "anonymous" 8037535Sdes 8164883Sdes#define FTP_CONNECTION_ALREADY_OPEN 125 8237573Sdes#define FTP_OPEN_DATA_CONNECTION 150 8337573Sdes#define FTP_OK 200 8441869Sdes#define FTP_FILE_STATUS 213 8541863Sdes#define FTP_SERVICE_READY 220 8667890Sdes#define FTP_TRANSFER_COMPLETE 226 8737573Sdes#define FTP_PASSIVE_MODE 227 8860737Sume#define FTP_LPASSIVE_MODE 228 8960737Sume#define FTP_EPASSIVE_MODE 229 9037573Sdes#define FTP_LOGGED_IN 230 9137573Sdes#define FTP_FILE_ACTION_OK 250 9237573Sdes#define FTP_NEED_PASSWORD 331 9337573Sdes#define FTP_NEED_ACCOUNT 332 9460188Sdes#define FTP_FILE_OK 350 9555557Sdes#define FTP_SYNTAX_ERROR 500 9663336Sdes#define FTP_PROTOCOL_ERROR 999 9737573Sdes 9840975Sdesstatic struct url cached_host; 9955557Sdesstatic int cached_socket; 10037535Sdes 10155557Sdesstatic char *last_reply; 10255557Sdesstatic size_t lr_size, lr_length; 10355557Sdesstatic int last_code; 10437571Sdes 10555557Sdes#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 10660707Sdes && isdigit(foo[2]) \ 10760707Sdes && (foo[3] == ' ' || foo[3] == '\0')) 10855557Sdes#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 10955557Sdes && isdigit(foo[2]) && foo[3] == '-') 11055557Sdes 11160737Sume/* translate IPv4 mapped IPv6 address to IPv4 address */ 11260737Sumestatic void 11360737Sumeunmappedaddr(struct sockaddr_in6 *sin6) 11460737Sume{ 11560737Sume struct sockaddr_in *sin4; 11660737Sume u_int32_t addr; 11760737Sume int port; 11860737Sume 11960737Sume if (sin6->sin6_family != AF_INET6 || 12060737Sume !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 12160737Sume return; 12260737Sume sin4 = (struct sockaddr_in *)sin6; 12360737Sume addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 12460737Sume port = sin6->sin6_port; 12560737Sume memset(sin4, 0, sizeof(struct sockaddr_in)); 12660737Sume sin4->sin_addr.s_addr = addr; 12760737Sume sin4->sin_port = port; 12860737Sume sin4->sin_family = AF_INET; 12960737Sume sin4->sin_len = sizeof(struct sockaddr_in); 13060737Sume} 13160737Sume 13237571Sdes/* 13355557Sdes * Get server response 13437535Sdes */ 13537535Sdesstatic int 13655557Sdes_ftp_chkerr(int cd) 13737535Sdes{ 13862215Sdes if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 13962215Sdes _fetch_syserr(); 14062215Sdes return -1; 14162215Sdes } 14262215Sdes if (isftpinfo(last_reply)) { 14369044Sdes while (lr_length && !isftpreply(last_reply)) { 14469043Sdes if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 14562215Sdes _fetch_syserr(); 14662215Sdes return -1; 14762215Sdes } 14862215Sdes } 14962215Sdes } 15055557Sdes 15155557Sdes while (lr_length && isspace(last_reply[lr_length-1])) 15255557Sdes lr_length--; 15355557Sdes last_reply[lr_length] = 0; 15437573Sdes 15555557Sdes if (!isftpreply(last_reply)) { 15663336Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 15737535Sdes return -1; 15837571Sdes } 15937535Sdes 16055557Sdes last_code = (last_reply[0] - '0') * 100 16155557Sdes + (last_reply[1] - '0') * 10 16255557Sdes + (last_reply[2] - '0'); 16355557Sdes 16455557Sdes return last_code; 16537535Sdes} 16637535Sdes 16737535Sdes/* 16837573Sdes * Send a command and check reply 16937535Sdes */ 17037535Sdesstatic int 17175891Sarchie_ftp_cmd(int cd, const char *fmt, ...) 17237535Sdes{ 17337573Sdes va_list ap; 17462982Sdes size_t len; 17555557Sdes char *msg; 17655557Sdes int r; 17737573Sdes 17837573Sdes va_start(ap, fmt); 17962982Sdes len = vasprintf(&msg, fmt, ap); 18055557Sdes va_end(ap); 18155557Sdes 18255557Sdes if (msg == NULL) { 18355557Sdes errno = ENOMEM; 18455557Sdes _fetch_syserr(); 18555557Sdes return -1; 18655557Sdes } 18762982Sdes 18862982Sdes r = _fetch_putln(cd, msg, len); 18955557Sdes free(msg); 19062982Sdes 19155557Sdes if (r == -1) { 19255557Sdes _fetch_syserr(); 19355557Sdes return -1; 19455557Sdes } 19537571Sdes 19655557Sdes return _ftp_chkerr(cd); 19737535Sdes} 19837535Sdes 19937535Sdes/* 20063340Sdes * Return a pointer to the filename part of a path 20163340Sdes */ 20275891Sarchiestatic const char * 20375891Sarchie_ftp_filename(const char *file) 20463340Sdes{ 20563340Sdes char *s; 20663340Sdes 20763340Sdes if ((s = strrchr(file, '/')) == NULL) 20863340Sdes return file; 20963340Sdes else 21063340Sdes return s + 1; 21163340Sdes} 21263340Sdes 21363340Sdes/* 21463340Sdes * Change working directory to the directory that contains the 21563340Sdes * specified file. 21663340Sdes */ 21763340Sdesstatic int 21875891Sarchie_ftp_cwd(int cd, const char *file) 21963340Sdes{ 22063340Sdes char *s; 22163340Sdes int e; 22263340Sdes 22363585Sdes if ((s = strrchr(file, '/')) == NULL || s == file) { 22463340Sdes e = _ftp_cmd(cd, "CWD /"); 22563340Sdes } else { 22663340Sdes e = _ftp_cmd(cd, "CWD %.*s", s - file, file); 22763340Sdes } 22863340Sdes if (e != FTP_FILE_ACTION_OK) { 22963340Sdes _ftp_seterr(e); 23063340Sdes return -1; 23163340Sdes } 23263340Sdes return 0; 23363340Sdes} 23463340Sdes 23563340Sdes/* 23663340Sdes * Request and parse file stats 23763340Sdes */ 23863340Sdesstatic int 23975891Sarchie_ftp_stat(int cd, const char *file, struct url_stat *us) 24063340Sdes{ 24175891Sarchie char *ln; 24275891Sarchie const char *s; 24363340Sdes struct tm tm; 24463340Sdes time_t t; 24563340Sdes int e; 24663340Sdes 24763392Sdes us->size = -1; 24863392Sdes us->atime = us->mtime = 0; 24963392Sdes 25063340Sdes if ((s = strrchr(file, '/')) == NULL) 25163340Sdes s = file; 25263340Sdes else 25363340Sdes ++s; 25463340Sdes 25563340Sdes if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) { 25663340Sdes _ftp_seterr(e); 25763340Sdes return -1; 25863340Sdes } 25963340Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 26063340Sdes /* nothing */ ; 26163340Sdes for (us->size = 0; *ln && isdigit(*ln); ln++) 26263340Sdes us->size = us->size * 10 + *ln - '0'; 26363340Sdes if (*ln && !isspace(*ln)) { 26463340Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 26575292Sdes us->size = -1; 26663340Sdes return -1; 26763340Sdes } 26863847Sdes if (us->size == 0) 26963847Sdes us->size = -1; 27085093Sdes DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", (long long)us->size)); 27163340Sdes 27263340Sdes if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) { 27363340Sdes _ftp_seterr(e); 27463340Sdes return -1; 27563340Sdes } 27663340Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 27763340Sdes /* nothing */ ; 27863340Sdes switch (strspn(ln, "0123456789")) { 27963340Sdes case 14: 28063340Sdes break; 28163340Sdes case 15: 28263340Sdes ln++; 28363340Sdes ln[0] = '2'; 28463340Sdes ln[1] = '0'; 28563340Sdes break; 28663340Sdes default: 28763340Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 28863340Sdes return -1; 28963340Sdes } 29063340Sdes if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 29163340Sdes &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 29263340Sdes &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { 29363340Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 29463340Sdes return -1; 29563340Sdes } 29663340Sdes tm.tm_mon--; 29763340Sdes tm.tm_year -= 1900; 29863340Sdes tm.tm_isdst = -1; 29963340Sdes t = timegm(&tm); 30063340Sdes if (t == (time_t)-1) 30163340Sdes t = time(NULL); 30263340Sdes us->mtime = t; 30363340Sdes us->atime = t; 30463340Sdes DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 30563340Sdes "%02d:%02d:%02d\033[m]\n", 30663340Sdes tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 30763340Sdes tm.tm_hour, tm.tm_min, tm.tm_sec)); 30863340Sdes return 0; 30963340Sdes} 31063340Sdes 31163340Sdes/* 31267430Sdes * I/O functions for FTP 31367430Sdes */ 31467430Sdesstruct ftpio { 31567430Sdes int csd; /* Control socket descriptor */ 31667430Sdes int dsd; /* Data socket descriptor */ 31767430Sdes int dir; /* Direction */ 31867430Sdes int eof; /* EOF reached */ 31967430Sdes int err; /* Error code */ 32067430Sdes}; 32167430Sdes 32267430Sdesstatic int _ftp_readfn(void *, char *, int); 32367430Sdesstatic int _ftp_writefn(void *, const char *, int); 32467430Sdesstatic fpos_t _ftp_seekfn(void *, fpos_t, int); 32567430Sdesstatic int _ftp_closefn(void *); 32667430Sdes 32767430Sdesstatic int 32867430Sdes_ftp_readfn(void *v, char *buf, int len) 32967430Sdes{ 33067430Sdes struct ftpio *io; 33167430Sdes int r; 33267430Sdes 33367430Sdes io = (struct ftpio *)v; 33467890Sdes if (io == NULL) { 33567890Sdes errno = EBADF; 33667890Sdes return -1; 33767890Sdes } 33867430Sdes if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) { 33967430Sdes errno = EBADF; 34067430Sdes return -1; 34167430Sdes } 34267430Sdes if (io->err) { 34367430Sdes errno = io->err; 34467430Sdes return -1; 34567430Sdes } 34667430Sdes if (io->eof) 34767430Sdes return 0; 34867430Sdes r = read(io->dsd, buf, len); 34967430Sdes if (r > 0) 35067430Sdes return r; 35167430Sdes if (r == 0) { 35267430Sdes io->eof = 1; 35378071Sdes return 0; 35467430Sdes } 35573934Sdes if (errno != EINTR) 35673934Sdes io->err = errno; 35767430Sdes return -1; 35867430Sdes} 35967430Sdes 36067430Sdesstatic int 36167430Sdes_ftp_writefn(void *v, const char *buf, int len) 36267430Sdes{ 36367430Sdes struct ftpio *io; 36467430Sdes int w; 36567430Sdes 36667430Sdes io = (struct ftpio *)v; 36767890Sdes if (io == NULL) { 36867890Sdes errno = EBADF; 36967890Sdes return -1; 37067890Sdes } 37167430Sdes if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) { 37267430Sdes errno = EBADF; 37367430Sdes return -1; 37467430Sdes } 37567430Sdes if (io->err) { 37667430Sdes errno = io->err; 37767430Sdes return -1; 37867430Sdes } 37967430Sdes w = write(io->dsd, buf, len); 38067430Sdes if (w >= 0) 38167430Sdes return w; 38273934Sdes if (errno != EINTR) 38373934Sdes io->err = errno; 38467430Sdes return -1; 38567430Sdes} 38667430Sdes 38767430Sdesstatic fpos_t 38885093Sdes_ftp_seekfn(void *v, fpos_t pos __unused, int whence __unused) 38967430Sdes{ 39067890Sdes struct ftpio *io; 39167890Sdes 39267890Sdes io = (struct ftpio *)v; 39367890Sdes if (io == NULL) { 39467890Sdes errno = EBADF; 39567890Sdes return -1; 39667890Sdes } 39767430Sdes errno = ESPIPE; 39867430Sdes return -1; 39967430Sdes} 40067430Sdes 40167430Sdesstatic int 40267430Sdes_ftp_closefn(void *v) 40367430Sdes{ 40467430Sdes struct ftpio *io; 40567890Sdes int r; 40667430Sdes 40767430Sdes io = (struct ftpio *)v; 40867890Sdes if (io == NULL) { 40967890Sdes errno = EBADF; 41067890Sdes return -1; 41167890Sdes } 41267430Sdes if (io->dir == -1) 41367430Sdes return 0; 41467430Sdes if (io->csd == -1 || io->dsd == -1) { 41567430Sdes errno = EBADF; 41667430Sdes return -1; 41767430Sdes } 41867707Sdes close(io->dsd); 41967430Sdes io->dir = -1; 42067430Sdes io->dsd = -1; 42167707Sdes DEBUG(fprintf(stderr, "Waiting for final status\n")); 42277234Sdes r = _ftp_chkerr(io->csd); 42367430Sdes close(io->csd); 42477234Sdes free(io); 42577234Sdes return (r == FTP_TRANSFER_COMPLETE) ? 0 : -1; 42667430Sdes} 42767430Sdes 42867430Sdesstatic FILE * 42967430Sdes_ftp_setup(int csd, int dsd, int mode) 43067430Sdes{ 43167430Sdes struct ftpio *io; 43267430Sdes FILE *f; 43367430Sdes 43467430Sdes if ((io = malloc(sizeof *io)) == NULL) 43567430Sdes return NULL; 43667430Sdes io->csd = dup(csd); 43767430Sdes io->dsd = dsd; 43867430Sdes io->dir = mode; 43967430Sdes io->eof = io->err = 0; 44067430Sdes f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn); 44167430Sdes if (f == NULL) 44267430Sdes free(io); 44367430Sdes return f; 44467430Sdes} 44567430Sdes 44667430Sdes/* 44737608Sdes * Transfer file 44837535Sdes */ 44937535Sdesstatic FILE * 45075891Sarchie_ftp_transfer(int cd, const char *oper, const char *file, 45175891Sarchie int mode, off_t offset, const char *flags) 45237535Sdes{ 45385093Sdes struct sockaddr_storage sa; 45460737Sume struct sockaddr_in6 *sin6; 45560737Sume struct sockaddr_in *sin4; 45674716Sdes int low, pasv, verbose; 45755544Sdes int e, sd = -1; 45855544Sdes socklen_t l; 45937573Sdes char *s; 46037573Sdes FILE *df; 46155544Sdes 46255544Sdes /* check flags */ 46374716Sdes low = CHECK_FLAG('l'); 46467892Sdes pasv = CHECK_FLAG('p'); 46567892Sdes verbose = CHECK_FLAG('v'); 46655544Sdes 46760951Sdes /* passive mode */ 46867259Sdes if (!pasv) 46969670Sdes pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL && 47067259Sdes strncasecmp(s, "no", 2) != 0); 47160951Sdes 47260737Sume /* find our own address, bind, and listen */ 47385093Sdes l = sizeof sa; 47485093Sdes if (getsockname(cd, (struct sockaddr *)&sa, &l) == -1) 47560737Sume goto sysouch; 47685093Sdes if (sa.ss_family == AF_INET6) 47785093Sdes unmappedaddr((struct sockaddr_in6 *)&sa); 47860737Sume 47937573Sdes /* open data socket */ 48085093Sdes if ((sd = socket(sa.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 48140939Sdes _fetch_syserr(); 48237573Sdes return NULL; 48337573Sdes } 48437573Sdes 48537573Sdes if (pasv) { 48660737Sume u_char addr[64]; 48737573Sdes char *ln, *p; 48885093Sdes unsigned int i; 48960737Sume int port; 49037573Sdes 49137573Sdes /* send PASV command */ 49255544Sdes if (verbose) 49355544Sdes _fetch_info("setting passive mode"); 49485093Sdes switch (sa.ss_family) { 49560737Sume case AF_INET: 49660737Sume if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 49760737Sume goto ouch; 49860737Sume break; 49960737Sume case AF_INET6: 50060737Sume if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 50160737Sume if (e == -1) 50260737Sume goto ouch; 50360737Sume if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 50460737Sume goto ouch; 50560737Sume } 50660737Sume break; 50760737Sume default: 50863336Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 50937573Sdes goto ouch; 51060737Sume } 51137573Sdes 51255544Sdes /* 51355544Sdes * Find address and port number. The reply to the PASV command 51455544Sdes * is IMHO the one and only weak point in the FTP protocol. 51555544Sdes */ 51655557Sdes ln = last_reply; 51762888Sume switch (e) { 51860737Sume case FTP_PASSIVE_MODE: 51960737Sume case FTP_LPASSIVE_MODE: 52062888Sume for (p = ln + 3; *p && !isdigit(*p); p++) 52162888Sume /* nothing */ ; 52262888Sume if (!*p) { 52363336Sdes e = FTP_PROTOCOL_ERROR; 52462888Sume goto ouch; 52562888Sume } 52660737Sume l = (e == FTP_PASSIVE_MODE ? 6 : 21); 52760737Sume for (i = 0; *p && i < l; i++, p++) 52860737Sume addr[i] = strtol(p, &p, 10); 52960737Sume if (i < l) { 53063336Sdes e = FTP_PROTOCOL_ERROR; 53160737Sume goto ouch; 53260737Sume } 53360737Sume break; 53460737Sume case FTP_EPASSIVE_MODE: 53562888Sume for (p = ln + 3; *p && *p != '('; p++) 53662888Sume /* nothing */ ; 53762888Sume if (!*p) { 53863336Sdes e = FTP_PROTOCOL_ERROR; 53962888Sume goto ouch; 54062888Sume } 54162888Sume ++p; 54260737Sume if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 54360737Sume &port, &addr[3]) != 5 || 54460737Sume addr[0] != addr[1] || 54560737Sume addr[0] != addr[2] || addr[0] != addr[3]) { 54663336Sdes e = FTP_PROTOCOL_ERROR; 54760737Sume goto ouch; 54860737Sume } 54960737Sume break; 55060737Sume } 55137573Sdes 55260188Sdes /* seek to required offset */ 55360188Sdes if (offset) 55460188Sdes if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 55560188Sdes goto sysouch; 55660188Sdes 55737573Sdes /* construct sockaddr for data socket */ 55885093Sdes l = sizeof sa; 55985093Sdes if (getpeername(cd, (struct sockaddr *)&sa, &l) == -1) 56037573Sdes goto sysouch; 56185093Sdes if (sa.ss_family == AF_INET6) 56285093Sdes unmappedaddr((struct sockaddr_in6 *)&sa); 56385093Sdes switch (sa.ss_family) { 56460737Sume case AF_INET6: 56585093Sdes sin6 = (struct sockaddr_in6 *)&sa; 56660737Sume if (e == FTP_EPASSIVE_MODE) 56760737Sume sin6->sin6_port = htons(port); 56860737Sume else { 56960737Sume bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 57060737Sume bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 57160737Sume } 57260737Sume break; 57360737Sume case AF_INET: 57485093Sdes sin4 = (struct sockaddr_in *)&sa; 57560737Sume if (e == FTP_EPASSIVE_MODE) 57660737Sume sin4->sin_port = htons(port); 57760737Sume else { 57860737Sume bcopy(addr, (char *)&sin4->sin_addr, 4); 57960737Sume bcopy(addr + 4, (char *)&sin4->sin_port, 2); 58060737Sume } 58160737Sume break; 58260737Sume default: 58363336Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 58460737Sume break; 58560737Sume } 58637573Sdes 58737573Sdes /* connect to data port */ 58855544Sdes if (verbose) 58955544Sdes _fetch_info("opening data connection"); 59085093Sdes if (connect(sd, (struct sockaddr *)&sa, sa.ss_len) == -1) 59137573Sdes goto sysouch; 59260188Sdes 59337573Sdes /* make the server initiate the transfer */ 59461866Sdes if (verbose) 59561866Sdes _fetch_info("initiating transfer"); 59663340Sdes e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 59764883Sdes if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) 59837573Sdes goto ouch; 59937573Sdes 60037573Sdes } else { 60137573Sdes u_int32_t a; 60237573Sdes u_short p; 60355544Sdes int arg, d; 60460737Sume char *ap; 60560737Sume char hname[INET6_ADDRSTRLEN]; 60637573Sdes 60785093Sdes switch (sa.ss_family) { 60860737Sume case AF_INET6: 60985093Sdes ((struct sockaddr_in6 *)&sa)->sin6_port = 0; 61060737Sume#ifdef IPV6_PORTRANGE 61174716Sdes arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH; 61260737Sume if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 61360737Sume (char *)&arg, sizeof(arg)) == -1) 61460737Sume goto sysouch; 61560737Sume#endif 61660737Sume break; 61760737Sume case AF_INET: 61885093Sdes ((struct sockaddr_in *)&sa)->sin_port = 0; 61974716Sdes arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH; 62060737Sume if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 62160737Sume (char *)&arg, sizeof arg) == -1) 62260737Sume goto sysouch; 62360737Sume break; 62460737Sume } 62555544Sdes if (verbose) 62655544Sdes _fetch_info("binding data socket"); 62785093Sdes if (bind(sd, (struct sockaddr *)&sa, sa.ss_len) == -1) 62837573Sdes goto sysouch; 62938394Sdes if (listen(sd, 1) == -1) 63037573Sdes goto sysouch; 63137573Sdes 63237573Sdes /* find what port we're on and tell the server */ 63385093Sdes if (getsockname(sd, (struct sockaddr *)&sa, &l) == -1) 63437573Sdes goto sysouch; 63585093Sdes switch (sa.ss_family) { 63660737Sume case AF_INET: 63785093Sdes sin4 = (struct sockaddr_in *)&sa; 63860737Sume a = ntohl(sin4->sin_addr.s_addr); 63960737Sume p = ntohs(sin4->sin_port); 64060737Sume e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 64160737Sume (a >> 24) & 0xff, (a >> 16) & 0xff, 64260737Sume (a >> 8) & 0xff, a & 0xff, 64360737Sume (p >> 8) & 0xff, p & 0xff); 64460737Sume break; 64560737Sume case AF_INET6: 64660737Sume#define UC(b) (((int)b)&0xff) 64760737Sume e = -1; 64885093Sdes sin6 = (struct sockaddr_in6 *)&sa; 64985093Sdes if (getnameinfo((struct sockaddr *)&sa, sa.ss_len, 65060737Sume hname, sizeof(hname), 65160737Sume NULL, 0, NI_NUMERICHOST) == 0) { 65260737Sume e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 65360737Sume htons(sin6->sin6_port)); 65460737Sume if (e == -1) 65560737Sume goto ouch; 65660737Sume } 65760737Sume if (e != FTP_OK) { 65860737Sume ap = (char *)&sin6->sin6_addr; 65960737Sume e = _ftp_cmd(cd, 66060737Sume "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 66160737Sume 6, 16, 66260737Sume UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 66360737Sume UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 66460737Sume UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 66560737Sume UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 66660737Sume 2, 66760737Sume (ntohs(sin6->sin6_port) >> 8) & 0xff, 66860737Sume ntohs(sin6->sin6_port) & 0xff); 66960737Sume } 67060737Sume break; 67160737Sume default: 67263336Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 67360737Sume goto ouch; 67460737Sume } 67541869Sdes if (e != FTP_OK) 67637573Sdes goto ouch; 67737573Sdes 67862256Sdes /* seek to required offset */ 67962256Sdes if (offset) 68062256Sdes if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 68162256Sdes goto sysouch; 68262256Sdes 68337573Sdes /* make the server initiate the transfer */ 68455544Sdes if (verbose) 68555544Sdes _fetch_info("initiating transfer"); 68663340Sdes e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 68741869Sdes if (e != FTP_OPEN_DATA_CONNECTION) 68837573Sdes goto ouch; 68937573Sdes 69037573Sdes /* accept the incoming connection and go to town */ 69138394Sdes if ((d = accept(sd, NULL, NULL)) == -1) 69237573Sdes goto sysouch; 69337573Sdes close(sd); 69437573Sdes sd = d; 69537573Sdes } 69637573Sdes 69767430Sdes if ((df = _ftp_setup(cd, sd, mode)) == NULL) 69837573Sdes goto sysouch; 69937573Sdes return df; 70037573Sdes 70137573Sdessysouch: 70240939Sdes _fetch_syserr(); 70360737Sume if (sd >= 0) 70460737Sume close(sd); 70541869Sdes return NULL; 70641869Sdes 70737573Sdesouch: 70855557Sdes if (e != -1) 70955557Sdes _ftp_seterr(e); 71060737Sume if (sd >= 0) 71160737Sume close(sd); 71237535Sdes return NULL; 71337535Sdes} 71437535Sdes 71537571Sdes/* 71677238Sdes * Authenticate 71777238Sdes */ 71877238Sdesstatic int 71977238Sdes_ftp_authenticate(int cd, struct url *url, struct url *purl) 72077238Sdes{ 72185093Sdes const char *user, *pwd, *logname; 72277238Sdes char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 72377238Sdes int e, len; 72477238Sdes 72577238Sdes /* XXX FTP_AUTH, and maybe .netrc */ 72677238Sdes 72777238Sdes /* send user name and password */ 72877238Sdes user = url->user; 72977238Sdes if (!user || !*user) 73077238Sdes user = getenv("FTP_LOGIN"); 73177238Sdes if (!user || !*user) 73277238Sdes user = FTP_ANONYMOUS_USER; 73377238Sdes if (purl && url->port == _fetch_default_port(url->scheme)) 73477238Sdes e = _ftp_cmd(cd, "USER %s@%s", user, url->host); 73577238Sdes else if (purl) 73677238Sdes e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port); 73777238Sdes else 73877238Sdes e = _ftp_cmd(cd, "USER %s", user); 73977238Sdes 74077238Sdes /* did the server request a password? */ 74177238Sdes if (e == FTP_NEED_PASSWORD) { 74277238Sdes pwd = url->pwd; 74377238Sdes if (!pwd || !*pwd) 74477238Sdes pwd = getenv("FTP_PASSWORD"); 74577238Sdes if (!pwd || !*pwd) { 74677238Sdes if ((logname = getlogin()) == 0) 74777238Sdes logname = FTP_ANONYMOUS_USER; 74881985Sbrian if ((len = snprintf(pbuf, MAXLOGNAME + 1, "%s@", logname)) < 0) 74981972Sbrian len = 0; 75081978Sbrian else if (len > MAXLOGNAME) 75181978Sbrian len = MAXLOGNAME; 75277238Sdes gethostname(pbuf + len, sizeof pbuf - len); 75377238Sdes pwd = pbuf; 75477238Sdes } 75577238Sdes e = _ftp_cmd(cd, "PASS %s", pwd); 75677238Sdes } 75777238Sdes 75877238Sdes return e; 75977238Sdes} 76077238Sdes 76177238Sdes/* 76237571Sdes * Log on to FTP server 76337535Sdes */ 76455557Sdesstatic int 76575891Sarchie_ftp_connect(struct url *url, struct url *purl, const char *flags) 76637571Sdes{ 76767043Sdes int cd, e, direct, verbose; 76860737Sume#ifdef INET6 76960737Sume int af = AF_UNSPEC; 77060737Sume#else 77160737Sume int af = AF_INET; 77260737Sume#endif 77337571Sdes 77467892Sdes direct = CHECK_FLAG('d'); 77567892Sdes verbose = CHECK_FLAG('v'); 77667892Sdes if (CHECK_FLAG('4')) 77760737Sume af = AF_INET; 77867892Sdes else if (CHECK_FLAG('6')) 77960737Sume af = AF_INET6; 78060737Sume 78167043Sdes if (direct) 78267043Sdes purl = NULL; 78367043Sdes 78437608Sdes /* check for proxy */ 78567043Sdes if (purl) { 78667043Sdes /* XXX proxy authentication! */ 78767043Sdes cd = _fetch_connect(purl->host, purl->port, af, verbose); 78837608Sdes } else { 78937608Sdes /* no proxy, go straight to target */ 79067043Sdes cd = _fetch_connect(url->host, url->port, af, verbose); 79167043Sdes purl = NULL; 79237608Sdes } 79337608Sdes 79437608Sdes /* check connection */ 79555557Sdes if (cd == -1) { 79640939Sdes _fetch_syserr(); 79737571Sdes return NULL; 79837571Sdes } 79937608Sdes 80037571Sdes /* expect welcome message */ 80155557Sdes if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 80237571Sdes goto fouch; 80337571Sdes 80477238Sdes /* authenticate */ 80577238Sdes if ((e = _ftp_authenticate(cd, url, purl)) != FTP_LOGGED_IN) 80641863Sdes goto fouch; 80737608Sdes 80837571Sdes /* might as well select mode and type at once */ 80937571Sdes#ifdef FTP_FORCE_STREAM_MODE 81055557Sdes if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 81141869Sdes goto fouch; 81237571Sdes#endif 81355557Sdes if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 81441869Sdes goto fouch; 81537571Sdes 81637571Sdes /* done */ 81755557Sdes return cd; 81837571Sdes 81937571Sdesfouch: 82055557Sdes if (e != -1) 82155557Sdes _ftp_seterr(e); 82255557Sdes close(cd); 82337571Sdes return NULL; 82437571Sdes} 82537571Sdes 82637571Sdes/* 82737571Sdes * Disconnect from server 82837571Sdes */ 82937571Sdesstatic void 83055557Sdes_ftp_disconnect(int cd) 83137571Sdes{ 83255557Sdes (void)_ftp_cmd(cd, "QUIT"); 83355557Sdes close(cd); 83437571Sdes} 83537571Sdes 83637571Sdes/* 83737571Sdes * Check if we're already connected 83837571Sdes */ 83937571Sdesstatic int 84040975Sdes_ftp_isconnected(struct url *url) 84137571Sdes{ 84237571Sdes return (cached_socket 84337571Sdes && (strcmp(url->host, cached_host.host) == 0) 84437571Sdes && (strcmp(url->user, cached_host.user) == 0) 84537571Sdes && (strcmp(url->pwd, cached_host.pwd) == 0) 84637571Sdes && (url->port == cached_host.port)); 84737571Sdes} 84837571Sdes 84937608Sdes/* 85041869Sdes * Check the cache, reconnect if no luck 85137608Sdes */ 85255557Sdesstatic int 85375891Sarchie_ftp_cached_connect(struct url *url, struct url *purl, const char *flags) 85437535Sdes{ 85555557Sdes int e, cd; 85637535Sdes 85755557Sdes cd = -1; 85841869Sdes 85937571Sdes /* set default port */ 86063842Sdes if (!url->port) 86168551Sdes url->port = _fetch_default_port(url->scheme); 86237535Sdes 86341863Sdes /* try to use previously cached connection */ 86455557Sdes if (_ftp_isconnected(url)) { 86555557Sdes e = _ftp_cmd(cached_socket, "NOOP"); 86655557Sdes if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 86767043Sdes return cached_socket; 86855557Sdes } 86937571Sdes 87037571Sdes /* connect to server */ 87167043Sdes if ((cd = _ftp_connect(url, purl, flags)) == -1) 87267043Sdes return -1; 87367043Sdes if (cached_socket) 87467043Sdes _ftp_disconnect(cached_socket); 87567043Sdes cached_socket = cd; 87667043Sdes memcpy(&cached_host, url, sizeof *url); 87755557Sdes return cd; 87837535Sdes} 87937535Sdes 88037571Sdes/* 88167043Sdes * Check the proxy settings 88263713Sdes */ 88367043Sdesstatic struct url * 88467043Sdes_ftp_get_proxy(void) 88563713Sdes{ 88667043Sdes struct url *purl; 88763713Sdes char *p; 88867043Sdes 88973932Sdes if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) || 89073932Sdes (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) && 89167043Sdes *p && (purl = fetchParseURL(p)) != NULL) { 89269272Sdes if (!*purl->scheme) { 89373932Sdes if (getenv("FTP_PROXY") || getenv("ftp_proxy")) 89469272Sdes strcpy(purl->scheme, SCHEME_FTP); 89569272Sdes else 89669272Sdes strcpy(purl->scheme, SCHEME_HTTP); 89769272Sdes } 89867043Sdes if (!purl->port) 89968551Sdes purl->port = _fetch_default_proxy_port(purl->scheme); 90067043Sdes if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 90167043Sdes strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 90267043Sdes return purl; 90367043Sdes fetchFreeURL(purl); 90467043Sdes } 90567043Sdes return NULL; 90663713Sdes} 90763713Sdes 90863713Sdes/* 90963340Sdes * Get and stat file 91037571Sdes */ 91137535SdesFILE * 91275891SarchiefetchXGetFTP(struct url *url, struct url_stat *us, const char *flags) 91337608Sdes{ 91467043Sdes struct url *purl; 91555557Sdes int cd; 91663713Sdes 91767043Sdes /* get the proxy URL, and check if we should use HTTP instead */ 91867892Sdes if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 91967043Sdes if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 92067043Sdes return _http_request(url, "GET", us, purl, flags); 92167043Sdes } else { 92267043Sdes purl = NULL; 92367043Sdes } 92455557Sdes 92541869Sdes /* connect to server */ 92667043Sdes cd = _ftp_cached_connect(url, purl, flags); 92767043Sdes if (purl) 92867043Sdes fetchFreeURL(purl); 92967043Sdes if (cd == NULL) 93041869Sdes return NULL; 93141869Sdes 93263340Sdes /* change directory */ 93363340Sdes if (_ftp_cwd(cd, url->doc) == -1) 93463340Sdes return NULL; 93563340Sdes 93663340Sdes /* stat file */ 93763392Sdes if (us && _ftp_stat(cd, url->doc, us) == -1 93863910Sdes && fetchLastErrCode != FETCH_PROTO 93963392Sdes && fetchLastErrCode != FETCH_UNAVAIL) 94063340Sdes return NULL; 94163340Sdes 94241869Sdes /* initiate the transfer */ 94367430Sdes return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags); 94437608Sdes} 94537608Sdes 94641869Sdes/* 94763340Sdes * Get file 94863340Sdes */ 94963340SdesFILE * 95075891SarchiefetchGetFTP(struct url *url, const char *flags) 95163340Sdes{ 95263340Sdes return fetchXGetFTP(url, NULL, flags); 95363340Sdes} 95463340Sdes 95563340Sdes/* 95641869Sdes * Put file 95741869Sdes */ 95837608SdesFILE * 95975891SarchiefetchPutFTP(struct url *url, const char *flags) 96037535Sdes{ 96167043Sdes struct url *purl; 96255557Sdes int cd; 96341869Sdes 96467043Sdes /* get the proxy URL, and check if we should use HTTP instead */ 96567892Sdes if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 96667043Sdes if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 96767043Sdes /* XXX HTTP PUT is not implemented, so try without the proxy */ 96867043Sdes purl = NULL; 96967043Sdes } else { 97067043Sdes purl = NULL; 97167043Sdes } 97263713Sdes 97341869Sdes /* connect to server */ 97467043Sdes cd = _ftp_cached_connect(url, purl, flags); 97567043Sdes if (purl) 97667043Sdes fetchFreeURL(purl); 97767043Sdes if (cd == NULL) 97841869Sdes return NULL; 97941869Sdes 98063340Sdes /* change directory */ 98163340Sdes if (_ftp_cwd(cd, url->doc) == -1) 98263340Sdes return NULL; 98363340Sdes 98441869Sdes /* initiate the transfer */ 98567892Sdes return _ftp_transfer(cd, CHECK_FLAG('a') ? "APPE" : "STOR", 98667430Sdes url->doc, O_WRONLY, url->offset, flags); 98737535Sdes} 98840975Sdes 98941869Sdes/* 99041869Sdes * Get file stats 99141869Sdes */ 99240975Sdesint 99375891SarchiefetchStatFTP(struct url *url, struct url_stat *us, const char *flags) 99440975Sdes{ 99567043Sdes struct url *purl; 99663340Sdes int cd; 99741869Sdes 99867043Sdes /* get the proxy URL, and check if we should use HTTP instead */ 99967892Sdes if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 100067043Sdes if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) { 100167043Sdes FILE *f; 100267043Sdes 100367043Sdes if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL) 100467043Sdes return -1; 100567043Sdes fclose(f); 100667043Sdes return 0; 100767043Sdes } 100867043Sdes } else { 100967043Sdes purl = NULL; 101067043Sdes } 101163713Sdes 101241869Sdes /* connect to server */ 101367043Sdes cd = _ftp_cached_connect(url, purl, flags); 101467043Sdes if (purl) 101567043Sdes fetchFreeURL(purl); 101667043Sdes if (cd == NULL) 101767043Sdes return NULL; 101867043Sdes 101941869Sdes /* change directory */ 102063340Sdes if (_ftp_cwd(cd, url->doc) == -1) 102141869Sdes return -1; 102241869Sdes 102363340Sdes /* stat file */ 102463340Sdes return _ftp_stat(cd, url->doc, us); 102540975Sdes} 102641989Sdes 102741989Sdes/* 102841989Sdes * List a directory 102941989Sdes */ 103041989Sdesstruct url_ent * 103185093SdesfetchListFTP(struct url *url __unused, const char *flags __unused) 103241989Sdes{ 103361866Sdes warnx("fetchListFTP(): not implemented"); 103461866Sdes return NULL; 103541989Sdes} 1036