ftp.c revision 75891
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 * 2850476Speter * $FreeBSD: head/lib/libfetch/ftp.c 75891 2001-04-24 00:06:21Z archie $ 2937535Sdes */ 3037535Sdes 3137535Sdes/* 3237571Sdes * Portions of this code were taken from or based on ftpio.c: 3337535Sdes * 3437535Sdes * ---------------------------------------------------------------------------- 3537535Sdes * "THE BEER-WARE LICENSE" (Revision 42): 3637535Sdes * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you 3737535Sdes * can do whatever you want with this stuff. If we meet some day, and you think 3837535Sdes * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 3937535Sdes * ---------------------------------------------------------------------------- 4037535Sdes * 4137535Sdes * Major Changelog: 4237535Sdes * 4337535Sdes * Dag-Erling Co�dan Sm�rgrav 4437535Sdes * 9 Jun 1998 4537535Sdes * 4637535Sdes * Incorporated into libfetch 4737535Sdes * 4837535Sdes * Jordan K. Hubbard 4937535Sdes * 17 Jan 1996 5037535Sdes * 5137535Sdes * Turned inside out. Now returns xfers as new file ids, not as a special 5237535Sdes * `state' of FTP_t 5337535Sdes * 5437535Sdes * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $ 5537535Sdes * 5637535Sdes */ 5737535Sdes 5841862Sdes#include <sys/param.h> 5937535Sdes#include <sys/socket.h> 6037535Sdes#include <netinet/in.h> 6137535Sdes 6237535Sdes#include <ctype.h> 6375891Sarchie#include <err.h> 6455557Sdes#include <errno.h> 6567430Sdes#include <fcntl.h> 6660188Sdes#include <netdb.h> 6737573Sdes#include <stdarg.h> 6837535Sdes#include <stdio.h> 6937571Sdes#include <stdlib.h> 7037535Sdes#include <string.h> 7141869Sdes#include <time.h> 7237571Sdes#include <unistd.h> 7337535Sdes 7437535Sdes#include "fetch.h" 7540939Sdes#include "common.h" 7641862Sdes#include "ftperr.h" 7737535Sdes 7870795Sdes#define FTP_ANONYMOUS_USER "anonymous" 7937535Sdes 8064883Sdes#define FTP_CONNECTION_ALREADY_OPEN 125 8137573Sdes#define FTP_OPEN_DATA_CONNECTION 150 8237573Sdes#define FTP_OK 200 8341869Sdes#define FTP_FILE_STATUS 213 8441863Sdes#define FTP_SERVICE_READY 220 8567890Sdes#define FTP_TRANSFER_COMPLETE 226 8637573Sdes#define FTP_PASSIVE_MODE 227 8760737Sume#define FTP_LPASSIVE_MODE 228 8860737Sume#define FTP_EPASSIVE_MODE 229 8937573Sdes#define FTP_LOGGED_IN 230 9037573Sdes#define FTP_FILE_ACTION_OK 250 9137573Sdes#define FTP_NEED_PASSWORD 331 9237573Sdes#define FTP_NEED_ACCOUNT 332 9360188Sdes#define FTP_FILE_OK 350 9455557Sdes#define FTP_SYNTAX_ERROR 500 9563336Sdes#define FTP_PROTOCOL_ERROR 999 9637573Sdes 9740975Sdesstatic struct url cached_host; 9855557Sdesstatic int cached_socket; 9937535Sdes 10055557Sdesstatic char *last_reply; 10155557Sdesstatic size_t lr_size, lr_length; 10255557Sdesstatic int last_code; 10337571Sdes 10455557Sdes#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 10560707Sdes && isdigit(foo[2]) \ 10660707Sdes && (foo[3] == ' ' || foo[3] == '\0')) 10755557Sdes#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 10855557Sdes && isdigit(foo[2]) && foo[3] == '-') 10955557Sdes 11060737Sume/* translate IPv4 mapped IPv6 address to IPv4 address */ 11160737Sumestatic void 11260737Sumeunmappedaddr(struct sockaddr_in6 *sin6) 11360737Sume{ 11460737Sume struct sockaddr_in *sin4; 11560737Sume u_int32_t addr; 11660737Sume int port; 11760737Sume 11860737Sume if (sin6->sin6_family != AF_INET6 || 11960737Sume !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 12060737Sume return; 12160737Sume sin4 = (struct sockaddr_in *)sin6; 12260737Sume addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 12360737Sume port = sin6->sin6_port; 12460737Sume memset(sin4, 0, sizeof(struct sockaddr_in)); 12560737Sume sin4->sin_addr.s_addr = addr; 12660737Sume sin4->sin_port = port; 12760737Sume sin4->sin_family = AF_INET; 12860737Sume sin4->sin_len = sizeof(struct sockaddr_in); 12960737Sume} 13060737Sume 13137571Sdes/* 13255557Sdes * Get server response 13337535Sdes */ 13437535Sdesstatic int 13555557Sdes_ftp_chkerr(int cd) 13637535Sdes{ 13762215Sdes if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 13862215Sdes _fetch_syserr(); 13962215Sdes return -1; 14062215Sdes } 14162215Sdes if (isftpinfo(last_reply)) { 14269044Sdes while (lr_length && !isftpreply(last_reply)) { 14369043Sdes if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 14462215Sdes _fetch_syserr(); 14562215Sdes return -1; 14662215Sdes } 14762215Sdes } 14862215Sdes } 14955557Sdes 15055557Sdes while (lr_length && isspace(last_reply[lr_length-1])) 15155557Sdes lr_length--; 15255557Sdes last_reply[lr_length] = 0; 15337573Sdes 15455557Sdes if (!isftpreply(last_reply)) { 15563336Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 15637535Sdes return -1; 15737571Sdes } 15837535Sdes 15955557Sdes last_code = (last_reply[0] - '0') * 100 16055557Sdes + (last_reply[1] - '0') * 10 16155557Sdes + (last_reply[2] - '0'); 16255557Sdes 16355557Sdes return last_code; 16437535Sdes} 16537535Sdes 16637535Sdes/* 16737573Sdes * Send a command and check reply 16837535Sdes */ 16937535Sdesstatic int 17075891Sarchie_ftp_cmd(int cd, const char *fmt, ...) 17137535Sdes{ 17237573Sdes va_list ap; 17362982Sdes size_t len; 17455557Sdes char *msg; 17555557Sdes int r; 17637573Sdes 17737573Sdes va_start(ap, fmt); 17862982Sdes len = vasprintf(&msg, fmt, ap); 17955557Sdes va_end(ap); 18055557Sdes 18155557Sdes if (msg == NULL) { 18255557Sdes errno = ENOMEM; 18355557Sdes _fetch_syserr(); 18455557Sdes return -1; 18555557Sdes } 18662982Sdes 18762982Sdes r = _fetch_putln(cd, msg, len); 18855557Sdes free(msg); 18962982Sdes 19055557Sdes if (r == -1) { 19155557Sdes _fetch_syserr(); 19255557Sdes return -1; 19355557Sdes } 19437571Sdes 19555557Sdes return _ftp_chkerr(cd); 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{ 20463340Sdes char *s; 20563340Sdes 20663340Sdes if ((s = strrchr(file, '/')) == NULL) 20763340Sdes return file; 20863340Sdes else 20963340Sdes return s + 1; 21063340Sdes} 21163340Sdes 21263340Sdes/* 21363340Sdes * Change working directory to the directory that contains the 21463340Sdes * specified file. 21563340Sdes */ 21663340Sdesstatic int 21775891Sarchie_ftp_cwd(int cd, const char *file) 21863340Sdes{ 21963340Sdes char *s; 22063340Sdes int e; 22163340Sdes 22263585Sdes if ((s = strrchr(file, '/')) == NULL || s == file) { 22363340Sdes e = _ftp_cmd(cd, "CWD /"); 22463340Sdes } else { 22563340Sdes e = _ftp_cmd(cd, "CWD %.*s", s - file, file); 22663340Sdes } 22763340Sdes if (e != FTP_FILE_ACTION_OK) { 22863340Sdes _ftp_seterr(e); 22963340Sdes return -1; 23063340Sdes } 23163340Sdes return 0; 23263340Sdes} 23363340Sdes 23463340Sdes/* 23563340Sdes * Request and parse file stats 23663340Sdes */ 23763340Sdesstatic int 23875891Sarchie_ftp_stat(int cd, const char *file, struct url_stat *us) 23963340Sdes{ 24075891Sarchie char *ln; 24175891Sarchie const char *s; 24263340Sdes struct tm tm; 24363340Sdes time_t t; 24463340Sdes int e; 24563340Sdes 24663392Sdes us->size = -1; 24763392Sdes us->atime = us->mtime = 0; 24863392Sdes 24963340Sdes if ((s = strrchr(file, '/')) == NULL) 25063340Sdes s = file; 25163340Sdes else 25263340Sdes ++s; 25363340Sdes 25463340Sdes if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) { 25563340Sdes _ftp_seterr(e); 25663340Sdes return -1; 25763340Sdes } 25863340Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 25963340Sdes /* nothing */ ; 26063340Sdes for (us->size = 0; *ln && isdigit(*ln); ln++) 26163340Sdes us->size = us->size * 10 + *ln - '0'; 26263340Sdes if (*ln && !isspace(*ln)) { 26363340Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 26475292Sdes us->size = -1; 26563340Sdes return -1; 26663340Sdes } 26763847Sdes if (us->size == 0) 26863847Sdes us->size = -1; 26963340Sdes DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); 27063340Sdes 27163340Sdes if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) { 27263340Sdes _ftp_seterr(e); 27363340Sdes return -1; 27463340Sdes } 27563340Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 27663340Sdes /* nothing */ ; 27763340Sdes switch (strspn(ln, "0123456789")) { 27863340Sdes case 14: 27963340Sdes break; 28063340Sdes case 15: 28163340Sdes ln++; 28263340Sdes ln[0] = '2'; 28363340Sdes ln[1] = '0'; 28463340Sdes break; 28563340Sdes default: 28663340Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 28763340Sdes return -1; 28863340Sdes } 28963340Sdes if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 29063340Sdes &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 29163340Sdes &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { 29263340Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 29363340Sdes return -1; 29463340Sdes } 29563340Sdes tm.tm_mon--; 29663340Sdes tm.tm_year -= 1900; 29763340Sdes tm.tm_isdst = -1; 29863340Sdes t = timegm(&tm); 29963340Sdes if (t == (time_t)-1) 30063340Sdes t = time(NULL); 30163340Sdes us->mtime = t; 30263340Sdes us->atime = t; 30363340Sdes DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 30463340Sdes "%02d:%02d:%02d\033[m]\n", 30563340Sdes tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 30663340Sdes tm.tm_hour, tm.tm_min, tm.tm_sec)); 30763340Sdes return 0; 30863340Sdes} 30963340Sdes 31063340Sdes/* 31167430Sdes * I/O functions for FTP 31267430Sdes */ 31367430Sdesstruct ftpio { 31467430Sdes int csd; /* Control socket descriptor */ 31567430Sdes int dsd; /* Data socket descriptor */ 31667430Sdes int dir; /* Direction */ 31767430Sdes int eof; /* EOF reached */ 31867430Sdes int err; /* Error code */ 31967430Sdes}; 32067430Sdes 32167430Sdesstatic int _ftp_readfn(void *, char *, int); 32267430Sdesstatic int _ftp_writefn(void *, const char *, int); 32367430Sdesstatic fpos_t _ftp_seekfn(void *, fpos_t, int); 32467430Sdesstatic int _ftp_closefn(void *); 32567430Sdes 32667430Sdesstatic int 32767430Sdes_ftp_readfn(void *v, char *buf, int len) 32867430Sdes{ 32967430Sdes struct ftpio *io; 33067430Sdes int r; 33167430Sdes 33267430Sdes io = (struct ftpio *)v; 33367890Sdes if (io == NULL) { 33467890Sdes errno = EBADF; 33567890Sdes return -1; 33667890Sdes } 33767430Sdes if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) { 33867430Sdes errno = EBADF; 33967430Sdes return -1; 34067430Sdes } 34167430Sdes if (io->err) { 34267430Sdes errno = io->err; 34367430Sdes return -1; 34467430Sdes } 34567430Sdes if (io->eof) 34667430Sdes return 0; 34767430Sdes r = read(io->dsd, buf, len); 34867430Sdes if (r > 0) 34967430Sdes return r; 35067430Sdes if (r == 0) { 35167430Sdes io->eof = 1; 35267430Sdes return _ftp_closefn(v); 35367430Sdes } 35473934Sdes if (errno != EINTR) 35573934Sdes io->err = errno; 35667430Sdes return -1; 35767430Sdes} 35867430Sdes 35967430Sdesstatic int 36067430Sdes_ftp_writefn(void *v, const char *buf, int len) 36167430Sdes{ 36267430Sdes struct ftpio *io; 36367430Sdes int w; 36467430Sdes 36567430Sdes io = (struct ftpio *)v; 36667890Sdes if (io == NULL) { 36767890Sdes errno = EBADF; 36867890Sdes return -1; 36967890Sdes } 37067430Sdes if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) { 37167430Sdes errno = EBADF; 37267430Sdes return -1; 37367430Sdes } 37467430Sdes if (io->err) { 37567430Sdes errno = io->err; 37667430Sdes return -1; 37767430Sdes } 37867430Sdes w = write(io->dsd, buf, len); 37967430Sdes if (w >= 0) 38067430Sdes return w; 38173934Sdes if (errno != EINTR) 38273934Sdes io->err = errno; 38367430Sdes return -1; 38467430Sdes} 38567430Sdes 38667430Sdesstatic fpos_t 38767430Sdes_ftp_seekfn(void *v, fpos_t pos, int whence) 38867430Sdes{ 38967890Sdes struct ftpio *io; 39067890Sdes 39167890Sdes io = (struct ftpio *)v; 39267890Sdes if (io == NULL) { 39367890Sdes errno = EBADF; 39467890Sdes return -1; 39567890Sdes } 39667430Sdes errno = ESPIPE; 39767430Sdes return -1; 39867430Sdes} 39967430Sdes 40067430Sdesstatic int 40167430Sdes_ftp_closefn(void *v) 40267430Sdes{ 40367430Sdes struct ftpio *io; 40467890Sdes int r; 40567430Sdes 40667430Sdes io = (struct ftpio *)v; 40767890Sdes if (io == NULL) { 40867890Sdes errno = EBADF; 40967890Sdes return -1; 41067890Sdes } 41167430Sdes if (io->dir == -1) 41267430Sdes return 0; 41367430Sdes if (io->csd == -1 || io->dsd == -1) { 41467430Sdes errno = EBADF; 41567430Sdes return -1; 41667430Sdes } 41767707Sdes close(io->dsd); 41867430Sdes io->dir = -1; 41967430Sdes io->dsd = -1; 42067707Sdes DEBUG(fprintf(stderr, "Waiting for final status\n")); 42167890Sdes if ((r = _ftp_chkerr(io->csd)) != FTP_TRANSFER_COMPLETE) 42267890Sdes io->err = r; 42367890Sdes else 42467890Sdes io->err = 0; 42567430Sdes close(io->csd); 42667430Sdes io->csd = -1; 42767430Sdes return io->err ? -1 : 0; 42867430Sdes} 42967430Sdes 43067430Sdesstatic FILE * 43167430Sdes_ftp_setup(int csd, int dsd, int mode) 43267430Sdes{ 43367430Sdes struct ftpio *io; 43467430Sdes FILE *f; 43567430Sdes 43667430Sdes if ((io = malloc(sizeof *io)) == NULL) 43767430Sdes return NULL; 43867430Sdes io->csd = dup(csd); 43967430Sdes io->dsd = dsd; 44067430Sdes io->dir = mode; 44167430Sdes io->eof = io->err = 0; 44267430Sdes f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn); 44367430Sdes if (f == NULL) 44467430Sdes free(io); 44567430Sdes return f; 44667430Sdes} 44767430Sdes 44867430Sdes/* 44937608Sdes * Transfer file 45037535Sdes */ 45137535Sdesstatic FILE * 45275891Sarchie_ftp_transfer(int cd, const char *oper, const char *file, 45375891Sarchie int mode, off_t offset, const char *flags) 45437535Sdes{ 45560737Sume struct sockaddr_storage sin; 45660737Sume struct sockaddr_in6 *sin6; 45760737Sume struct sockaddr_in *sin4; 45874716Sdes int low, pasv, verbose; 45955544Sdes int e, sd = -1; 46055544Sdes socklen_t l; 46137573Sdes char *s; 46237573Sdes FILE *df; 46355544Sdes 46455544Sdes /* check flags */ 46574716Sdes low = CHECK_FLAG('l'); 46667892Sdes pasv = CHECK_FLAG('p'); 46767892Sdes verbose = CHECK_FLAG('v'); 46855544Sdes 46960951Sdes /* passive mode */ 47067259Sdes if (!pasv) 47169670Sdes pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL && 47267259Sdes strncasecmp(s, "no", 2) != 0); 47360951Sdes 47460737Sume /* find our own address, bind, and listen */ 47560737Sume l = sizeof sin; 47660737Sume if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 47760737Sume goto sysouch; 47860737Sume if (sin.ss_family == AF_INET6) 47960737Sume unmappedaddr((struct sockaddr_in6 *)&sin); 48060737Sume 48137573Sdes /* open data socket */ 48260737Sume if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 48340939Sdes _fetch_syserr(); 48437573Sdes return NULL; 48537573Sdes } 48637573Sdes 48737573Sdes if (pasv) { 48860737Sume u_char addr[64]; 48937573Sdes char *ln, *p; 49037573Sdes int i; 49160737Sume int port; 49237573Sdes 49337573Sdes /* send PASV command */ 49455544Sdes if (verbose) 49555544Sdes _fetch_info("setting passive mode"); 49660737Sume switch (sin.ss_family) { 49760737Sume case AF_INET: 49860737Sume if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 49960737Sume goto ouch; 50060737Sume break; 50160737Sume case AF_INET6: 50260737Sume if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 50360737Sume if (e == -1) 50460737Sume goto ouch; 50560737Sume if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 50660737Sume goto ouch; 50760737Sume } 50860737Sume break; 50960737Sume default: 51063336Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 51137573Sdes goto ouch; 51260737Sume } 51337573Sdes 51455544Sdes /* 51555544Sdes * Find address and port number. The reply to the PASV command 51655544Sdes * is IMHO the one and only weak point in the FTP protocol. 51755544Sdes */ 51855557Sdes ln = last_reply; 51962888Sume switch (e) { 52060737Sume case FTP_PASSIVE_MODE: 52160737Sume case FTP_LPASSIVE_MODE: 52262888Sume for (p = ln + 3; *p && !isdigit(*p); p++) 52362888Sume /* nothing */ ; 52462888Sume if (!*p) { 52563336Sdes e = FTP_PROTOCOL_ERROR; 52662888Sume goto ouch; 52762888Sume } 52860737Sume l = (e == FTP_PASSIVE_MODE ? 6 : 21); 52960737Sume for (i = 0; *p && i < l; i++, p++) 53060737Sume addr[i] = strtol(p, &p, 10); 53160737Sume if (i < l) { 53263336Sdes e = FTP_PROTOCOL_ERROR; 53360737Sume goto ouch; 53460737Sume } 53560737Sume break; 53660737Sume case FTP_EPASSIVE_MODE: 53762888Sume for (p = ln + 3; *p && *p != '('; p++) 53862888Sume /* nothing */ ; 53962888Sume if (!*p) { 54063336Sdes e = FTP_PROTOCOL_ERROR; 54162888Sume goto ouch; 54262888Sume } 54362888Sume ++p; 54460737Sume if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 54560737Sume &port, &addr[3]) != 5 || 54660737Sume addr[0] != addr[1] || 54760737Sume addr[0] != addr[2] || addr[0] != addr[3]) { 54863336Sdes e = FTP_PROTOCOL_ERROR; 54960737Sume goto ouch; 55060737Sume } 55160737Sume break; 55260737Sume } 55337573Sdes 55460188Sdes /* seek to required offset */ 55560188Sdes if (offset) 55660188Sdes if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 55760188Sdes goto sysouch; 55860188Sdes 55937573Sdes /* construct sockaddr for data socket */ 56060188Sdes l = sizeof sin; 56155557Sdes if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 56237573Sdes goto sysouch; 56360737Sume if (sin.ss_family == AF_INET6) 56460737Sume unmappedaddr((struct sockaddr_in6 *)&sin); 56560737Sume switch (sin.ss_family) { 56660737Sume case AF_INET6: 56760737Sume sin6 = (struct sockaddr_in6 *)&sin; 56860737Sume if (e == FTP_EPASSIVE_MODE) 56960737Sume sin6->sin6_port = htons(port); 57060737Sume else { 57160737Sume bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 57260737Sume bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 57360737Sume } 57460737Sume break; 57560737Sume case AF_INET: 57660737Sume sin4 = (struct sockaddr_in *)&sin; 57760737Sume if (e == FTP_EPASSIVE_MODE) 57860737Sume sin4->sin_port = htons(port); 57960737Sume else { 58060737Sume bcopy(addr, (char *)&sin4->sin_addr, 4); 58160737Sume bcopy(addr + 4, (char *)&sin4->sin_port, 2); 58260737Sume } 58360737Sume break; 58460737Sume default: 58563336Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 58660737Sume break; 58760737Sume } 58837573Sdes 58937573Sdes /* connect to data port */ 59055544Sdes if (verbose) 59155544Sdes _fetch_info("opening data connection"); 59260737Sume if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 59337573Sdes goto sysouch; 59460188Sdes 59537573Sdes /* make the server initiate the transfer */ 59661866Sdes if (verbose) 59761866Sdes _fetch_info("initiating transfer"); 59863340Sdes e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 59964883Sdes if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) 60037573Sdes goto ouch; 60137573Sdes 60237573Sdes } else { 60337573Sdes u_int32_t a; 60437573Sdes u_short p; 60555544Sdes int arg, d; 60660737Sume char *ap; 60760737Sume char hname[INET6_ADDRSTRLEN]; 60837573Sdes 60960737Sume switch (sin.ss_family) { 61060737Sume case AF_INET6: 61160737Sume ((struct sockaddr_in6 *)&sin)->sin6_port = 0; 61260737Sume#ifdef IPV6_PORTRANGE 61374716Sdes arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH; 61460737Sume if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 61560737Sume (char *)&arg, sizeof(arg)) == -1) 61660737Sume goto sysouch; 61760737Sume#endif 61860737Sume break; 61960737Sume case AF_INET: 62060737Sume ((struct sockaddr_in *)&sin)->sin_port = 0; 62174716Sdes arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH; 62260737Sume if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 62360737Sume (char *)&arg, sizeof arg) == -1) 62460737Sume goto sysouch; 62560737Sume break; 62660737Sume } 62755544Sdes if (verbose) 62855544Sdes _fetch_info("binding data socket"); 62960737Sume if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 63037573Sdes goto sysouch; 63138394Sdes if (listen(sd, 1) == -1) 63237573Sdes goto sysouch; 63337573Sdes 63437573Sdes /* find what port we're on and tell the server */ 63538394Sdes if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 63637573Sdes goto sysouch; 63760737Sume switch (sin.ss_family) { 63860737Sume case AF_INET: 63960737Sume sin4 = (struct sockaddr_in *)&sin; 64060737Sume a = ntohl(sin4->sin_addr.s_addr); 64160737Sume p = ntohs(sin4->sin_port); 64260737Sume e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 64360737Sume (a >> 24) & 0xff, (a >> 16) & 0xff, 64460737Sume (a >> 8) & 0xff, a & 0xff, 64560737Sume (p >> 8) & 0xff, p & 0xff); 64660737Sume break; 64760737Sume case AF_INET6: 64860737Sume#define UC(b) (((int)b)&0xff) 64960737Sume e = -1; 65060737Sume sin6 = (struct sockaddr_in6 *)&sin; 65160737Sume if (getnameinfo((struct sockaddr *)&sin, sin.ss_len, 65260737Sume hname, sizeof(hname), 65360737Sume NULL, 0, NI_NUMERICHOST) == 0) { 65460737Sume e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 65560737Sume htons(sin6->sin6_port)); 65660737Sume if (e == -1) 65760737Sume goto ouch; 65860737Sume } 65960737Sume if (e != FTP_OK) { 66060737Sume ap = (char *)&sin6->sin6_addr; 66160737Sume e = _ftp_cmd(cd, 66260737Sume "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 66360737Sume 6, 16, 66460737Sume UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 66560737Sume UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 66660737Sume UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 66760737Sume UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 66860737Sume 2, 66960737Sume (ntohs(sin6->sin6_port) >> 8) & 0xff, 67060737Sume ntohs(sin6->sin6_port) & 0xff); 67160737Sume } 67260737Sume break; 67360737Sume default: 67463336Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 67560737Sume goto ouch; 67660737Sume } 67741869Sdes if (e != FTP_OK) 67837573Sdes goto ouch; 67937573Sdes 68062256Sdes /* seek to required offset */ 68162256Sdes if (offset) 68262256Sdes if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 68362256Sdes goto sysouch; 68462256Sdes 68537573Sdes /* make the server initiate the transfer */ 68655544Sdes if (verbose) 68755544Sdes _fetch_info("initiating transfer"); 68863340Sdes e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 68941869Sdes if (e != FTP_OPEN_DATA_CONNECTION) 69037573Sdes goto ouch; 69137573Sdes 69237573Sdes /* accept the incoming connection and go to town */ 69338394Sdes if ((d = accept(sd, NULL, NULL)) == -1) 69437573Sdes goto sysouch; 69537573Sdes close(sd); 69637573Sdes sd = d; 69737573Sdes } 69837573Sdes 69967430Sdes if ((df = _ftp_setup(cd, sd, mode)) == NULL) 70037573Sdes goto sysouch; 70137573Sdes return df; 70237573Sdes 70337573Sdessysouch: 70440939Sdes _fetch_syserr(); 70560737Sume if (sd >= 0) 70660737Sume close(sd); 70741869Sdes return NULL; 70841869Sdes 70937573Sdesouch: 71055557Sdes if (e != -1) 71155557Sdes _ftp_seterr(e); 71260737Sume if (sd >= 0) 71360737Sume close(sd); 71437535Sdes return NULL; 71537535Sdes} 71637535Sdes 71737571Sdes/* 71837571Sdes * Log on to FTP server 71937535Sdes */ 72055557Sdesstatic int 72175891Sarchie_ftp_connect(struct url *url, struct url *purl, const char *flags) 72237571Sdes{ 72367043Sdes int cd, e, direct, verbose; 72460737Sume#ifdef INET6 72560737Sume int af = AF_UNSPEC; 72660737Sume#else 72760737Sume int af = AF_INET; 72860737Sume#endif 72960791Sume const char *logname; 73075891Sarchie const char *user; 73175891Sarchie const char *pwd; 73260791Sume char localhost[MAXHOSTNAMELEN]; 73360791Sume char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 73437571Sdes 73567892Sdes direct = CHECK_FLAG('d'); 73667892Sdes verbose = CHECK_FLAG('v'); 73767892Sdes if (CHECK_FLAG('4')) 73860737Sume af = AF_INET; 73967892Sdes else if (CHECK_FLAG('6')) 74060737Sume af = AF_INET6; 74160737Sume 74267043Sdes if (direct) 74367043Sdes purl = NULL; 74467043Sdes 74537608Sdes /* check for proxy */ 74667043Sdes if (purl) { 74767043Sdes /* XXX proxy authentication! */ 74867043Sdes cd = _fetch_connect(purl->host, purl->port, af, verbose); 74937608Sdes } else { 75037608Sdes /* no proxy, go straight to target */ 75167043Sdes cd = _fetch_connect(url->host, url->port, af, verbose); 75267043Sdes purl = NULL; 75337608Sdes } 75437608Sdes 75537608Sdes /* check connection */ 75655557Sdes if (cd == -1) { 75740939Sdes _fetch_syserr(); 75837571Sdes return NULL; 75937571Sdes } 76037608Sdes 76137571Sdes /* expect welcome message */ 76255557Sdes if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 76337571Sdes goto fouch; 76467043Sdes 76567043Sdes /* XXX FTP_AUTH, and maybe .netrc */ 76637571Sdes 76737571Sdes /* send user name and password */ 76867043Sdes user = url->user; 76937608Sdes if (!user || !*user) 77070273Sdes user = getenv("FTP_LOGIN"); 77170273Sdes if (!user || !*user) 77237608Sdes user = FTP_ANONYMOUS_USER; 77368551Sdes if (purl && url->port == _fetch_default_port(url->scheme)) 77467055Sdes e = _ftp_cmd(cd, "USER %s@%s", user, url->host); 77567043Sdes else if (purl) 77667055Sdes e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port); 77763712Sdes else 77867055Sdes e = _ftp_cmd(cd, "USER %s", user); 77937608Sdes 78037608Sdes /* did the server request a password? */ 78137608Sdes if (e == FTP_NEED_PASSWORD) { 78267043Sdes pwd = url->pwd; 78337608Sdes if (!pwd || !*pwd) 78460791Sume pwd = getenv("FTP_PASSWORD"); 78560791Sume if (!pwd || !*pwd) { 78660791Sume if ((logname = getlogin()) == 0) 78770795Sdes logname = FTP_ANONYMOUS_USER; 78860791Sume gethostname(localhost, sizeof localhost); 78960791Sume snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost); 79060791Sume pwd = pbuf; 79160791Sume } 79255557Sdes e = _ftp_cmd(cd, "PASS %s", pwd); 79337608Sdes } 79437608Sdes 79537608Sdes /* did the server request an account? */ 79641869Sdes if (e == FTP_NEED_ACCOUNT) 79741863Sdes goto fouch; 79837608Sdes 79937608Sdes /* we should be done by now */ 80041869Sdes if (e != FTP_LOGGED_IN) 80137571Sdes goto fouch; 80237571Sdes 80337571Sdes /* might as well select mode and type at once */ 80437571Sdes#ifdef FTP_FORCE_STREAM_MODE 80555557Sdes if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 80641869Sdes goto fouch; 80737571Sdes#endif 80855557Sdes if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 80941869Sdes goto fouch; 81037571Sdes 81137571Sdes /* done */ 81255557Sdes return cd; 81337571Sdes 81437571Sdesfouch: 81555557Sdes if (e != -1) 81655557Sdes _ftp_seterr(e); 81755557Sdes close(cd); 81837571Sdes return NULL; 81937571Sdes} 82037571Sdes 82137571Sdes/* 82237571Sdes * Disconnect from server 82337571Sdes */ 82437571Sdesstatic void 82555557Sdes_ftp_disconnect(int cd) 82637571Sdes{ 82755557Sdes (void)_ftp_cmd(cd, "QUIT"); 82855557Sdes close(cd); 82937571Sdes} 83037571Sdes 83137571Sdes/* 83237571Sdes * Check if we're already connected 83337571Sdes */ 83437571Sdesstatic int 83540975Sdes_ftp_isconnected(struct url *url) 83637571Sdes{ 83737571Sdes return (cached_socket 83837571Sdes && (strcmp(url->host, cached_host.host) == 0) 83937571Sdes && (strcmp(url->user, cached_host.user) == 0) 84037571Sdes && (strcmp(url->pwd, cached_host.pwd) == 0) 84137571Sdes && (url->port == cached_host.port)); 84237571Sdes} 84337571Sdes 84437608Sdes/* 84541869Sdes * Check the cache, reconnect if no luck 84637608Sdes */ 84755557Sdesstatic int 84875891Sarchie_ftp_cached_connect(struct url *url, struct url *purl, const char *flags) 84937535Sdes{ 85055557Sdes int e, cd; 85137535Sdes 85255557Sdes cd = -1; 85341869Sdes 85437571Sdes /* set default port */ 85563842Sdes if (!url->port) 85668551Sdes url->port = _fetch_default_port(url->scheme); 85737535Sdes 85841863Sdes /* try to use previously cached connection */ 85955557Sdes if (_ftp_isconnected(url)) { 86055557Sdes e = _ftp_cmd(cached_socket, "NOOP"); 86155557Sdes if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 86267043Sdes return cached_socket; 86355557Sdes } 86437571Sdes 86537571Sdes /* connect to server */ 86667043Sdes if ((cd = _ftp_connect(url, purl, flags)) == -1) 86767043Sdes return -1; 86867043Sdes if (cached_socket) 86967043Sdes _ftp_disconnect(cached_socket); 87067043Sdes cached_socket = cd; 87167043Sdes memcpy(&cached_host, url, sizeof *url); 87255557Sdes return cd; 87337535Sdes} 87437535Sdes 87537571Sdes/* 87667043Sdes * Check the proxy settings 87763713Sdes */ 87867043Sdesstatic struct url * 87967043Sdes_ftp_get_proxy(void) 88063713Sdes{ 88167043Sdes struct url *purl; 88263713Sdes char *p; 88367043Sdes 88473932Sdes if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) || 88573932Sdes (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) && 88667043Sdes *p && (purl = fetchParseURL(p)) != NULL) { 88769272Sdes if (!*purl->scheme) { 88873932Sdes if (getenv("FTP_PROXY") || getenv("ftp_proxy")) 88969272Sdes strcpy(purl->scheme, SCHEME_FTP); 89069272Sdes else 89169272Sdes strcpy(purl->scheme, SCHEME_HTTP); 89269272Sdes } 89367043Sdes if (!purl->port) 89468551Sdes purl->port = _fetch_default_proxy_port(purl->scheme); 89567043Sdes if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 89667043Sdes strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 89767043Sdes return purl; 89867043Sdes fetchFreeURL(purl); 89967043Sdes } 90067043Sdes return NULL; 90163713Sdes} 90263713Sdes 90363713Sdes/* 90463340Sdes * Get and stat file 90537571Sdes */ 90637535SdesFILE * 90775891SarchiefetchXGetFTP(struct url *url, struct url_stat *us, const char *flags) 90837608Sdes{ 90967043Sdes struct url *purl; 91055557Sdes int cd; 91163713Sdes 91267043Sdes /* get the proxy URL, and check if we should use HTTP instead */ 91367892Sdes if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 91467043Sdes if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 91567043Sdes return _http_request(url, "GET", us, purl, flags); 91667043Sdes } else { 91767043Sdes purl = NULL; 91867043Sdes } 91955557Sdes 92041869Sdes /* connect to server */ 92167043Sdes cd = _ftp_cached_connect(url, purl, flags); 92267043Sdes if (purl) 92367043Sdes fetchFreeURL(purl); 92467043Sdes if (cd == NULL) 92541869Sdes return NULL; 92641869Sdes 92763340Sdes /* change directory */ 92863340Sdes if (_ftp_cwd(cd, url->doc) == -1) 92963340Sdes return NULL; 93063340Sdes 93163340Sdes /* stat file */ 93263392Sdes if (us && _ftp_stat(cd, url->doc, us) == -1 93363910Sdes && fetchLastErrCode != FETCH_PROTO 93463392Sdes && fetchLastErrCode != FETCH_UNAVAIL) 93563340Sdes return NULL; 93663340Sdes 93741869Sdes /* initiate the transfer */ 93867430Sdes return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags); 93937608Sdes} 94037608Sdes 94141869Sdes/* 94263340Sdes * Get file 94363340Sdes */ 94463340SdesFILE * 94575891SarchiefetchGetFTP(struct url *url, const char *flags) 94663340Sdes{ 94763340Sdes return fetchXGetFTP(url, NULL, flags); 94863340Sdes} 94963340Sdes 95063340Sdes/* 95141869Sdes * Put file 95241869Sdes */ 95337608SdesFILE * 95475891SarchiefetchPutFTP(struct url *url, const char *flags) 95537535Sdes{ 95667043Sdes struct url *purl; 95755557Sdes int cd; 95841869Sdes 95967043Sdes /* get the proxy URL, and check if we should use HTTP instead */ 96067892Sdes if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 96167043Sdes if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 96267043Sdes /* XXX HTTP PUT is not implemented, so try without the proxy */ 96367043Sdes purl = NULL; 96467043Sdes } else { 96567043Sdes purl = NULL; 96667043Sdes } 96763713Sdes 96841869Sdes /* connect to server */ 96967043Sdes cd = _ftp_cached_connect(url, purl, flags); 97067043Sdes if (purl) 97167043Sdes fetchFreeURL(purl); 97267043Sdes if (cd == NULL) 97341869Sdes return NULL; 97441869Sdes 97563340Sdes /* change directory */ 97663340Sdes if (_ftp_cwd(cd, url->doc) == -1) 97763340Sdes return NULL; 97863340Sdes 97941869Sdes /* initiate the transfer */ 98067892Sdes return _ftp_transfer(cd, CHECK_FLAG('a') ? "APPE" : "STOR", 98167430Sdes url->doc, O_WRONLY, url->offset, flags); 98237535Sdes} 98340975Sdes 98441869Sdes/* 98541869Sdes * Get file stats 98641869Sdes */ 98740975Sdesint 98875891SarchiefetchStatFTP(struct url *url, struct url_stat *us, const char *flags) 98940975Sdes{ 99067043Sdes struct url *purl; 99163340Sdes int cd; 99241869Sdes 99367043Sdes /* get the proxy URL, and check if we should use HTTP instead */ 99467892Sdes if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 99567043Sdes if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) { 99667043Sdes FILE *f; 99767043Sdes 99867043Sdes if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL) 99967043Sdes return -1; 100067043Sdes fclose(f); 100167043Sdes return 0; 100267043Sdes } 100367043Sdes } else { 100467043Sdes purl = NULL; 100567043Sdes } 100663713Sdes 100741869Sdes /* connect to server */ 100867043Sdes cd = _ftp_cached_connect(url, purl, flags); 100967043Sdes if (purl) 101067043Sdes fetchFreeURL(purl); 101167043Sdes if (cd == NULL) 101267043Sdes return NULL; 101367043Sdes 101441869Sdes /* change directory */ 101563340Sdes if (_ftp_cwd(cd, url->doc) == -1) 101641869Sdes return -1; 101741869Sdes 101863340Sdes /* stat file */ 101963340Sdes return _ftp_stat(cd, url->doc, us); 102040975Sdes} 102141989Sdes 102241989Sdes/* 102341989Sdes * List a directory 102441989Sdes */ 102541989Sdesstruct url_ent * 102675891SarchiefetchListFTP(struct url *url, const char *flags) 102741989Sdes{ 102861866Sdes warnx("fetchListFTP(): not implemented"); 102961866Sdes return NULL; 103041989Sdes} 1031