ftp.c revision 69272
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 69272 2000-11-27 13:42:56Z des $ 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> 6355557Sdes#include <errno.h> 6467430Sdes#include <fcntl.h> 6560188Sdes#include <netdb.h> 6637573Sdes#include <stdarg.h> 6737535Sdes#include <stdio.h> 6837571Sdes#include <stdlib.h> 6937535Sdes#include <string.h> 7041869Sdes#include <time.h> 7137571Sdes#include <unistd.h> 7237535Sdes 7337535Sdes#include "fetch.h" 7440939Sdes#include "common.h" 7541862Sdes#include "ftperr.h" 7637535Sdes 7737535Sdes#define FTP_ANONYMOUS_USER "ftp" 7837535Sdes#define FTP_ANONYMOUS_PASSWORD "ftp" 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 17055557Sdes_ftp_cmd(int cd, 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 */ 20163340Sdesstatic char * 20263340Sdes_ftp_filename(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 21763340Sdes_ftp_cwd(int cd, 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 23863340Sdes_ftp_stat(int cd, char *file, struct url_stat *us) 23963340Sdes{ 24063340Sdes char *ln, *s; 24163340Sdes struct tm tm; 24263340Sdes time_t t; 24363340Sdes int e; 24463340Sdes 24563392Sdes us->size = -1; 24663392Sdes us->atime = us->mtime = 0; 24763392Sdes 24863340Sdes if ((s = strrchr(file, '/')) == NULL) 24963340Sdes s = file; 25063340Sdes else 25163340Sdes ++s; 25263340Sdes 25363340Sdes if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) { 25463340Sdes _ftp_seterr(e); 25563340Sdes return -1; 25663340Sdes } 25763340Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 25863340Sdes /* nothing */ ; 25963340Sdes for (us->size = 0; *ln && isdigit(*ln); ln++) 26063340Sdes us->size = us->size * 10 + *ln - '0'; 26163340Sdes if (*ln && !isspace(*ln)) { 26263340Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 26363340Sdes return -1; 26463340Sdes } 26563847Sdes if (us->size == 0) 26663847Sdes us->size = -1; 26763340Sdes DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); 26863340Sdes 26963340Sdes if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) { 27063340Sdes _ftp_seterr(e); 27163340Sdes return -1; 27263340Sdes } 27363340Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 27463340Sdes /* nothing */ ; 27563340Sdes switch (strspn(ln, "0123456789")) { 27663340Sdes case 14: 27763340Sdes break; 27863340Sdes case 15: 27963340Sdes ln++; 28063340Sdes ln[0] = '2'; 28163340Sdes ln[1] = '0'; 28263340Sdes break; 28363340Sdes default: 28463340Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 28563340Sdes return -1; 28663340Sdes } 28763340Sdes if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 28863340Sdes &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 28963340Sdes &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { 29063340Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 29163340Sdes return -1; 29263340Sdes } 29363340Sdes tm.tm_mon--; 29463340Sdes tm.tm_year -= 1900; 29563340Sdes tm.tm_isdst = -1; 29663340Sdes t = timegm(&tm); 29763340Sdes if (t == (time_t)-1) 29863340Sdes t = time(NULL); 29963340Sdes us->mtime = t; 30063340Sdes us->atime = t; 30163340Sdes DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 30263340Sdes "%02d:%02d:%02d\033[m]\n", 30363340Sdes tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 30463340Sdes tm.tm_hour, tm.tm_min, tm.tm_sec)); 30563340Sdes return 0; 30663340Sdes} 30763340Sdes 30863340Sdes/* 30967430Sdes * I/O functions for FTP 31067430Sdes */ 31167430Sdesstruct ftpio { 31267430Sdes int csd; /* Control socket descriptor */ 31367430Sdes int dsd; /* Data socket descriptor */ 31467430Sdes int dir; /* Direction */ 31567430Sdes int eof; /* EOF reached */ 31667430Sdes int err; /* Error code */ 31767430Sdes}; 31867430Sdes 31967430Sdesstatic int _ftp_readfn(void *, char *, int); 32067430Sdesstatic int _ftp_writefn(void *, const char *, int); 32167430Sdesstatic fpos_t _ftp_seekfn(void *, fpos_t, int); 32267430Sdesstatic int _ftp_closefn(void *); 32367430Sdes 32467430Sdesstatic int 32567430Sdes_ftp_readfn(void *v, char *buf, int len) 32667430Sdes{ 32767430Sdes struct ftpio *io; 32867430Sdes int r; 32967430Sdes 33067430Sdes io = (struct ftpio *)v; 33167890Sdes if (io == NULL) { 33267890Sdes errno = EBADF; 33367890Sdes return -1; 33467890Sdes } 33567430Sdes if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) { 33667430Sdes errno = EBADF; 33767430Sdes return -1; 33867430Sdes } 33967430Sdes if (io->err) { 34067430Sdes errno = io->err; 34167430Sdes return -1; 34267430Sdes } 34367430Sdes if (io->eof) 34467430Sdes return 0; 34567430Sdes r = read(io->dsd, buf, len); 34667430Sdes if (r > 0) 34767430Sdes return r; 34867430Sdes if (r == 0) { 34967430Sdes io->eof = 1; 35067430Sdes return _ftp_closefn(v); 35167430Sdes } 35267430Sdes io->err = errno; 35367430Sdes return -1; 35467430Sdes} 35567430Sdes 35667430Sdesstatic int 35767430Sdes_ftp_writefn(void *v, const char *buf, int len) 35867430Sdes{ 35967430Sdes struct ftpio *io; 36067430Sdes int w; 36167430Sdes 36267430Sdes io = (struct ftpio *)v; 36367890Sdes if (io == NULL) { 36467890Sdes errno = EBADF; 36567890Sdes return -1; 36667890Sdes } 36767430Sdes if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) { 36867430Sdes errno = EBADF; 36967430Sdes return -1; 37067430Sdes } 37167430Sdes if (io->err) { 37267430Sdes errno = io->err; 37367430Sdes return -1; 37467430Sdes } 37567430Sdes w = write(io->dsd, buf, len); 37667430Sdes if (w >= 0) 37767430Sdes return w; 37867430Sdes io->err = errno; 37967430Sdes return -1; 38067430Sdes} 38167430Sdes 38267430Sdesstatic fpos_t 38367430Sdes_ftp_seekfn(void *v, fpos_t pos, int whence) 38467430Sdes{ 38567890Sdes struct ftpio *io; 38667890Sdes 38767890Sdes io = (struct ftpio *)v; 38867890Sdes if (io == NULL) { 38967890Sdes errno = EBADF; 39067890Sdes return -1; 39167890Sdes } 39267430Sdes errno = ESPIPE; 39367430Sdes return -1; 39467430Sdes} 39567430Sdes 39667430Sdesstatic int 39767430Sdes_ftp_closefn(void *v) 39867430Sdes{ 39967430Sdes struct ftpio *io; 40067890Sdes int r; 40167430Sdes 40267430Sdes io = (struct ftpio *)v; 40367890Sdes if (io == NULL) { 40467890Sdes errno = EBADF; 40567890Sdes return -1; 40667890Sdes } 40767430Sdes if (io->dir == -1) 40867430Sdes return 0; 40967430Sdes if (io->csd == -1 || io->dsd == -1) { 41067430Sdes errno = EBADF; 41167430Sdes return -1; 41267430Sdes } 41367707Sdes close(io->dsd); 41467430Sdes io->dir = -1; 41567430Sdes io->dsd = -1; 41667707Sdes DEBUG(fprintf(stderr, "Waiting for final status\n")); 41767890Sdes if ((r = _ftp_chkerr(io->csd)) != FTP_TRANSFER_COMPLETE) 41867890Sdes io->err = r; 41967890Sdes else 42067890Sdes io->err = 0; 42167430Sdes close(io->csd); 42267430Sdes io->csd = -1; 42367430Sdes return io->err ? -1 : 0; 42467430Sdes} 42567430Sdes 42667430Sdesstatic FILE * 42767430Sdes_ftp_setup(int csd, int dsd, int mode) 42867430Sdes{ 42967430Sdes struct ftpio *io; 43067430Sdes FILE *f; 43167430Sdes 43267430Sdes if ((io = malloc(sizeof *io)) == NULL) 43367430Sdes return NULL; 43467430Sdes io->csd = dup(csd); 43567430Sdes io->dsd = dsd; 43667430Sdes io->dir = mode; 43767430Sdes io->eof = io->err = 0; 43867430Sdes f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn); 43967430Sdes if (f == NULL) 44067430Sdes free(io); 44167430Sdes return f; 44267430Sdes} 44367430Sdes 44467430Sdes/* 44537608Sdes * Transfer file 44637535Sdes */ 44737535Sdesstatic FILE * 44860188Sdes_ftp_transfer(int cd, char *oper, char *file, 44967430Sdes int mode, off_t offset, char *flags) 45037535Sdes{ 45160737Sume struct sockaddr_storage sin; 45260737Sume struct sockaddr_in6 *sin6; 45360737Sume struct sockaddr_in *sin4; 45455544Sdes int pasv, high, verbose; 45555544Sdes int e, sd = -1; 45655544Sdes socklen_t l; 45737573Sdes char *s; 45837573Sdes FILE *df; 45955544Sdes 46055544Sdes /* check flags */ 46167892Sdes pasv = CHECK_FLAG('p'); 46267892Sdes high = CHECK_FLAG('h'); 46367892Sdes verbose = CHECK_FLAG('v'); 46455544Sdes 46560951Sdes /* passive mode */ 46667259Sdes if (!pasv) 46767259Sdes pasv = ((s = getenv("FTP_PASSIVE_MODE")) == NULL || 46867259Sdes strncasecmp(s, "no", 2) != 0); 46960951Sdes 47060737Sume /* find our own address, bind, and listen */ 47160737Sume l = sizeof sin; 47260737Sume if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 47360737Sume goto sysouch; 47460737Sume if (sin.ss_family == AF_INET6) 47560737Sume unmappedaddr((struct sockaddr_in6 *)&sin); 47660737Sume 47737573Sdes /* open data socket */ 47860737Sume if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 47940939Sdes _fetch_syserr(); 48037573Sdes return NULL; 48137573Sdes } 48237573Sdes 48337573Sdes if (pasv) { 48460737Sume u_char addr[64]; 48537573Sdes char *ln, *p; 48637573Sdes int i; 48760737Sume int port; 48837573Sdes 48937573Sdes /* send PASV command */ 49055544Sdes if (verbose) 49155544Sdes _fetch_info("setting passive mode"); 49260737Sume switch (sin.ss_family) { 49360737Sume case AF_INET: 49460737Sume if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 49560737Sume goto ouch; 49660737Sume break; 49760737Sume case AF_INET6: 49860737Sume if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 49960737Sume if (e == -1) 50060737Sume goto ouch; 50160737Sume if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 50260737Sume goto ouch; 50360737Sume } 50460737Sume break; 50560737Sume default: 50663336Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 50737573Sdes goto ouch; 50860737Sume } 50937573Sdes 51055544Sdes /* 51155544Sdes * Find address and port number. The reply to the PASV command 51255544Sdes * is IMHO the one and only weak point in the FTP protocol. 51355544Sdes */ 51455557Sdes ln = last_reply; 51562888Sume switch (e) { 51660737Sume case FTP_PASSIVE_MODE: 51760737Sume case FTP_LPASSIVE_MODE: 51862888Sume for (p = ln + 3; *p && !isdigit(*p); p++) 51962888Sume /* nothing */ ; 52062888Sume if (!*p) { 52163336Sdes e = FTP_PROTOCOL_ERROR; 52262888Sume goto ouch; 52362888Sume } 52460737Sume l = (e == FTP_PASSIVE_MODE ? 6 : 21); 52560737Sume for (i = 0; *p && i < l; i++, p++) 52660737Sume addr[i] = strtol(p, &p, 10); 52760737Sume if (i < l) { 52863336Sdes e = FTP_PROTOCOL_ERROR; 52960737Sume goto ouch; 53060737Sume } 53160737Sume break; 53260737Sume case FTP_EPASSIVE_MODE: 53362888Sume for (p = ln + 3; *p && *p != '('; p++) 53462888Sume /* nothing */ ; 53562888Sume if (!*p) { 53663336Sdes e = FTP_PROTOCOL_ERROR; 53762888Sume goto ouch; 53862888Sume } 53962888Sume ++p; 54060737Sume if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 54160737Sume &port, &addr[3]) != 5 || 54260737Sume addr[0] != addr[1] || 54360737Sume addr[0] != addr[2] || addr[0] != addr[3]) { 54463336Sdes e = FTP_PROTOCOL_ERROR; 54560737Sume goto ouch; 54660737Sume } 54760737Sume break; 54860737Sume } 54937573Sdes 55060188Sdes /* seek to required offset */ 55160188Sdes if (offset) 55260188Sdes if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 55360188Sdes goto sysouch; 55460188Sdes 55537573Sdes /* construct sockaddr for data socket */ 55660188Sdes l = sizeof sin; 55755557Sdes if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 55837573Sdes goto sysouch; 55960737Sume if (sin.ss_family == AF_INET6) 56060737Sume unmappedaddr((struct sockaddr_in6 *)&sin); 56160737Sume switch (sin.ss_family) { 56260737Sume case AF_INET6: 56360737Sume sin6 = (struct sockaddr_in6 *)&sin; 56460737Sume if (e == FTP_EPASSIVE_MODE) 56560737Sume sin6->sin6_port = htons(port); 56660737Sume else { 56760737Sume bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 56860737Sume bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 56960737Sume } 57060737Sume break; 57160737Sume case AF_INET: 57260737Sume sin4 = (struct sockaddr_in *)&sin; 57360737Sume if (e == FTP_EPASSIVE_MODE) 57460737Sume sin4->sin_port = htons(port); 57560737Sume else { 57660737Sume bcopy(addr, (char *)&sin4->sin_addr, 4); 57760737Sume bcopy(addr + 4, (char *)&sin4->sin_port, 2); 57860737Sume } 57960737Sume break; 58060737Sume default: 58163336Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 58260737Sume break; 58360737Sume } 58437573Sdes 58537573Sdes /* connect to data port */ 58655544Sdes if (verbose) 58755544Sdes _fetch_info("opening data connection"); 58860737Sume if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 58937573Sdes goto sysouch; 59060188Sdes 59137573Sdes /* make the server initiate the transfer */ 59261866Sdes if (verbose) 59361866Sdes _fetch_info("initiating transfer"); 59463340Sdes e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 59564883Sdes if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) 59637573Sdes goto ouch; 59737573Sdes 59837573Sdes } else { 59937573Sdes u_int32_t a; 60037573Sdes u_short p; 60155544Sdes int arg, d; 60260737Sume char *ap; 60360737Sume char hname[INET6_ADDRSTRLEN]; 60437573Sdes 60560737Sume switch (sin.ss_family) { 60660737Sume case AF_INET6: 60760737Sume ((struct sockaddr_in6 *)&sin)->sin6_port = 0; 60860737Sume#ifdef IPV6_PORTRANGE 60960737Sume arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT; 61060737Sume if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 61160737Sume (char *)&arg, sizeof(arg)) == -1) 61260737Sume goto sysouch; 61360737Sume#endif 61460737Sume break; 61560737Sume case AF_INET: 61660737Sume ((struct sockaddr_in *)&sin)->sin_port = 0; 61760737Sume arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; 61860737Sume if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 61960737Sume (char *)&arg, sizeof arg) == -1) 62060737Sume goto sysouch; 62160737Sume break; 62260737Sume } 62355544Sdes if (verbose) 62455544Sdes _fetch_info("binding data socket"); 62560737Sume if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 62637573Sdes goto sysouch; 62738394Sdes if (listen(sd, 1) == -1) 62837573Sdes goto sysouch; 62937573Sdes 63037573Sdes /* find what port we're on and tell the server */ 63138394Sdes if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 63237573Sdes goto sysouch; 63360737Sume switch (sin.ss_family) { 63460737Sume case AF_INET: 63560737Sume sin4 = (struct sockaddr_in *)&sin; 63660737Sume a = ntohl(sin4->sin_addr.s_addr); 63760737Sume p = ntohs(sin4->sin_port); 63860737Sume e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 63960737Sume (a >> 24) & 0xff, (a >> 16) & 0xff, 64060737Sume (a >> 8) & 0xff, a & 0xff, 64160737Sume (p >> 8) & 0xff, p & 0xff); 64260737Sume break; 64360737Sume case AF_INET6: 64460737Sume#define UC(b) (((int)b)&0xff) 64560737Sume e = -1; 64660737Sume sin6 = (struct sockaddr_in6 *)&sin; 64760737Sume if (getnameinfo((struct sockaddr *)&sin, sin.ss_len, 64860737Sume hname, sizeof(hname), 64960737Sume NULL, 0, NI_NUMERICHOST) == 0) { 65060737Sume e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 65160737Sume htons(sin6->sin6_port)); 65260737Sume if (e == -1) 65360737Sume goto ouch; 65460737Sume } 65560737Sume if (e != FTP_OK) { 65660737Sume ap = (char *)&sin6->sin6_addr; 65760737Sume e = _ftp_cmd(cd, 65860737Sume "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 65960737Sume 6, 16, 66060737Sume UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 66160737Sume UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 66260737Sume UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 66360737Sume UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 66460737Sume 2, 66560737Sume (ntohs(sin6->sin6_port) >> 8) & 0xff, 66660737Sume ntohs(sin6->sin6_port) & 0xff); 66760737Sume } 66860737Sume break; 66960737Sume default: 67063336Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 67160737Sume goto ouch; 67260737Sume } 67341869Sdes if (e != FTP_OK) 67437573Sdes goto ouch; 67537573Sdes 67662256Sdes /* seek to required offset */ 67762256Sdes if (offset) 67862256Sdes if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 67962256Sdes goto sysouch; 68062256Sdes 68137573Sdes /* make the server initiate the transfer */ 68255544Sdes if (verbose) 68355544Sdes _fetch_info("initiating transfer"); 68463340Sdes e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 68541869Sdes if (e != FTP_OPEN_DATA_CONNECTION) 68637573Sdes goto ouch; 68737573Sdes 68837573Sdes /* accept the incoming connection and go to town */ 68938394Sdes if ((d = accept(sd, NULL, NULL)) == -1) 69037573Sdes goto sysouch; 69137573Sdes close(sd); 69237573Sdes sd = d; 69337573Sdes } 69437573Sdes 69567430Sdes if ((df = _ftp_setup(cd, sd, mode)) == NULL) 69637573Sdes goto sysouch; 69737573Sdes return df; 69837573Sdes 69937573Sdessysouch: 70040939Sdes _fetch_syserr(); 70160737Sume if (sd >= 0) 70260737Sume close(sd); 70341869Sdes return NULL; 70441869Sdes 70537573Sdesouch: 70655557Sdes if (e != -1) 70755557Sdes _ftp_seterr(e); 70860737Sume if (sd >= 0) 70960737Sume close(sd); 71037535Sdes return NULL; 71137535Sdes} 71237535Sdes 71337571Sdes/* 71437571Sdes * Log on to FTP server 71537535Sdes */ 71655557Sdesstatic int 71767043Sdes_ftp_connect(struct url *url, struct url *purl, char *flags) 71837571Sdes{ 71967043Sdes int cd, e, direct, verbose; 72060737Sume#ifdef INET6 72160737Sume int af = AF_UNSPEC; 72260737Sume#else 72360737Sume int af = AF_INET; 72460737Sume#endif 72560791Sume const char *logname; 72667043Sdes char *user, *pwd; 72760791Sume char localhost[MAXHOSTNAMELEN]; 72860791Sume char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 72937571Sdes 73067892Sdes direct = CHECK_FLAG('d'); 73167892Sdes verbose = CHECK_FLAG('v'); 73267892Sdes if (CHECK_FLAG('4')) 73360737Sume af = AF_INET; 73467892Sdes else if (CHECK_FLAG('6')) 73560737Sume af = AF_INET6; 73660737Sume 73767043Sdes if (direct) 73867043Sdes purl = NULL; 73967043Sdes 74037608Sdes /* check for proxy */ 74167043Sdes if (purl) { 74267043Sdes /* XXX proxy authentication! */ 74367043Sdes cd = _fetch_connect(purl->host, purl->port, af, verbose); 74437608Sdes } else { 74537608Sdes /* no proxy, go straight to target */ 74667043Sdes cd = _fetch_connect(url->host, url->port, af, verbose); 74767043Sdes purl = NULL; 74837608Sdes } 74937608Sdes 75037608Sdes /* check connection */ 75155557Sdes if (cd == -1) { 75240939Sdes _fetch_syserr(); 75337571Sdes return NULL; 75437571Sdes } 75537608Sdes 75637571Sdes /* expect welcome message */ 75755557Sdes if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 75837571Sdes goto fouch; 75967043Sdes 76067043Sdes /* XXX FTP_AUTH, and maybe .netrc */ 76137571Sdes 76237571Sdes /* send user name and password */ 76367043Sdes user = url->user; 76437608Sdes if (!user || !*user) 76537608Sdes user = FTP_ANONYMOUS_USER; 76668551Sdes if (purl && url->port == _fetch_default_port(url->scheme)) 76767055Sdes e = _ftp_cmd(cd, "USER %s@%s", user, url->host); 76867043Sdes else if (purl) 76967055Sdes e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port); 77063712Sdes else 77167055Sdes e = _ftp_cmd(cd, "USER %s", user); 77237608Sdes 77337608Sdes /* did the server request a password? */ 77437608Sdes if (e == FTP_NEED_PASSWORD) { 77567043Sdes pwd = url->pwd; 77637608Sdes if (!pwd || !*pwd) 77760791Sume pwd = getenv("FTP_PASSWORD"); 77860791Sume if (!pwd || !*pwd) { 77960791Sume if ((logname = getlogin()) == 0) 78060791Sume logname = FTP_ANONYMOUS_PASSWORD; 78160791Sume gethostname(localhost, sizeof localhost); 78260791Sume snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost); 78360791Sume pwd = pbuf; 78460791Sume } 78555557Sdes e = _ftp_cmd(cd, "PASS %s", pwd); 78637608Sdes } 78737608Sdes 78837608Sdes /* did the server request an account? */ 78941869Sdes if (e == FTP_NEED_ACCOUNT) 79041863Sdes goto fouch; 79137608Sdes 79237608Sdes /* we should be done by now */ 79341869Sdes if (e != FTP_LOGGED_IN) 79437571Sdes goto fouch; 79537571Sdes 79637571Sdes /* might as well select mode and type at once */ 79737571Sdes#ifdef FTP_FORCE_STREAM_MODE 79855557Sdes if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 79941869Sdes goto fouch; 80037571Sdes#endif 80155557Sdes if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 80241869Sdes goto fouch; 80337571Sdes 80437571Sdes /* done */ 80555557Sdes return cd; 80637571Sdes 80737571Sdesfouch: 80855557Sdes if (e != -1) 80955557Sdes _ftp_seterr(e); 81055557Sdes close(cd); 81137571Sdes return NULL; 81237571Sdes} 81337571Sdes 81437571Sdes/* 81537571Sdes * Disconnect from server 81637571Sdes */ 81737571Sdesstatic void 81855557Sdes_ftp_disconnect(int cd) 81937571Sdes{ 82055557Sdes (void)_ftp_cmd(cd, "QUIT"); 82155557Sdes close(cd); 82237571Sdes} 82337571Sdes 82437571Sdes/* 82537571Sdes * Check if we're already connected 82637571Sdes */ 82737571Sdesstatic int 82840975Sdes_ftp_isconnected(struct url *url) 82937571Sdes{ 83037571Sdes return (cached_socket 83137571Sdes && (strcmp(url->host, cached_host.host) == 0) 83237571Sdes && (strcmp(url->user, cached_host.user) == 0) 83337571Sdes && (strcmp(url->pwd, cached_host.pwd) == 0) 83437571Sdes && (url->port == cached_host.port)); 83537571Sdes} 83637571Sdes 83737608Sdes/* 83841869Sdes * Check the cache, reconnect if no luck 83937608Sdes */ 84055557Sdesstatic int 84167043Sdes_ftp_cached_connect(struct url *url, struct url *purl, char *flags) 84237535Sdes{ 84355557Sdes int e, cd; 84437535Sdes 84555557Sdes cd = -1; 84641869Sdes 84737571Sdes /* set default port */ 84863842Sdes if (!url->port) 84968551Sdes url->port = _fetch_default_port(url->scheme); 85037535Sdes 85141863Sdes /* try to use previously cached connection */ 85255557Sdes if (_ftp_isconnected(url)) { 85355557Sdes e = _ftp_cmd(cached_socket, "NOOP"); 85455557Sdes if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 85567043Sdes return cached_socket; 85655557Sdes } 85737571Sdes 85837571Sdes /* connect to server */ 85967043Sdes if ((cd = _ftp_connect(url, purl, flags)) == -1) 86067043Sdes return -1; 86167043Sdes if (cached_socket) 86267043Sdes _ftp_disconnect(cached_socket); 86367043Sdes cached_socket = cd; 86467043Sdes memcpy(&cached_host, url, sizeof *url); 86555557Sdes return cd; 86637535Sdes} 86737535Sdes 86837571Sdes/* 86967043Sdes * Check the proxy settings 87063713Sdes */ 87167043Sdesstatic struct url * 87267043Sdes_ftp_get_proxy(void) 87363713Sdes{ 87467043Sdes struct url *purl; 87563713Sdes char *p; 87667043Sdes 87767043Sdes if (((p = getenv("FTP_PROXY")) || (p = getenv("HTTP_PROXY"))) && 87867043Sdes *p && (purl = fetchParseURL(p)) != NULL) { 87969272Sdes if (!*purl->scheme) { 88069272Sdes if (getenv("FTP_PROXY")) 88169272Sdes strcpy(purl->scheme, SCHEME_FTP); 88269272Sdes else 88369272Sdes strcpy(purl->scheme, SCHEME_HTTP); 88469272Sdes } 88567043Sdes if (!purl->port) 88668551Sdes purl->port = _fetch_default_proxy_port(purl->scheme); 88767043Sdes if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 88867043Sdes strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 88967043Sdes return purl; 89067043Sdes fetchFreeURL(purl); 89167043Sdes } 89267043Sdes return NULL; 89363713Sdes} 89463713Sdes 89563713Sdes/* 89663340Sdes * Get and stat file 89737571Sdes */ 89837535SdesFILE * 89963340SdesfetchXGetFTP(struct url *url, struct url_stat *us, char *flags) 90037608Sdes{ 90167043Sdes struct url *purl; 90255557Sdes int cd; 90363713Sdes 90467043Sdes /* get the proxy URL, and check if we should use HTTP instead */ 90567892Sdes if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 90667043Sdes if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 90767043Sdes return _http_request(url, "GET", us, purl, flags); 90867043Sdes } else { 90967043Sdes purl = NULL; 91067043Sdes } 91155557Sdes 91241869Sdes /* connect to server */ 91367043Sdes cd = _ftp_cached_connect(url, purl, flags); 91467043Sdes if (purl) 91567043Sdes fetchFreeURL(purl); 91667043Sdes if (cd == NULL) 91741869Sdes return NULL; 91841869Sdes 91963340Sdes /* change directory */ 92063340Sdes if (_ftp_cwd(cd, url->doc) == -1) 92163340Sdes return NULL; 92263340Sdes 92363340Sdes /* stat file */ 92463392Sdes if (us && _ftp_stat(cd, url->doc, us) == -1 92563910Sdes && fetchLastErrCode != FETCH_PROTO 92663392Sdes && fetchLastErrCode != FETCH_UNAVAIL) 92763340Sdes return NULL; 92863340Sdes 92941869Sdes /* initiate the transfer */ 93067430Sdes return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags); 93137608Sdes} 93237608Sdes 93341869Sdes/* 93463340Sdes * Get file 93563340Sdes */ 93663340SdesFILE * 93763340SdesfetchGetFTP(struct url *url, char *flags) 93863340Sdes{ 93963340Sdes return fetchXGetFTP(url, NULL, flags); 94063340Sdes} 94163340Sdes 94263340Sdes/* 94341869Sdes * Put file 94441869Sdes */ 94537608SdesFILE * 94640975SdesfetchPutFTP(struct url *url, char *flags) 94737535Sdes{ 94867043Sdes struct url *purl; 94955557Sdes int cd; 95041869Sdes 95167043Sdes /* get the proxy URL, and check if we should use HTTP instead */ 95267892Sdes if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 95367043Sdes if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 95467043Sdes /* XXX HTTP PUT is not implemented, so try without the proxy */ 95567043Sdes purl = NULL; 95667043Sdes } else { 95767043Sdes purl = NULL; 95867043Sdes } 95963713Sdes 96041869Sdes /* connect to server */ 96167043Sdes cd = _ftp_cached_connect(url, purl, flags); 96267043Sdes if (purl) 96367043Sdes fetchFreeURL(purl); 96467043Sdes if (cd == NULL) 96541869Sdes return NULL; 96641869Sdes 96763340Sdes /* change directory */ 96863340Sdes if (_ftp_cwd(cd, url->doc) == -1) 96963340Sdes return NULL; 97063340Sdes 97141869Sdes /* initiate the transfer */ 97267892Sdes return _ftp_transfer(cd, CHECK_FLAG('a') ? "APPE" : "STOR", 97367430Sdes url->doc, O_WRONLY, url->offset, flags); 97437535Sdes} 97540975Sdes 97641869Sdes/* 97741869Sdes * Get file stats 97841869Sdes */ 97940975Sdesint 98040975SdesfetchStatFTP(struct url *url, struct url_stat *us, char *flags) 98140975Sdes{ 98267043Sdes struct url *purl; 98363340Sdes int cd; 98441869Sdes 98567043Sdes /* get the proxy URL, and check if we should use HTTP instead */ 98667892Sdes if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 98767043Sdes if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) { 98867043Sdes FILE *f; 98967043Sdes 99067043Sdes if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL) 99167043Sdes return -1; 99267043Sdes fclose(f); 99367043Sdes return 0; 99467043Sdes } 99567043Sdes } else { 99667043Sdes purl = NULL; 99767043Sdes } 99863713Sdes 99941869Sdes /* connect to server */ 100067043Sdes cd = _ftp_cached_connect(url, purl, flags); 100167043Sdes if (purl) 100267043Sdes fetchFreeURL(purl); 100367043Sdes if (cd == NULL) 100467043Sdes return NULL; 100567043Sdes 100641869Sdes /* change directory */ 100763340Sdes if (_ftp_cwd(cd, url->doc) == -1) 100841869Sdes return -1; 100941869Sdes 101063340Sdes /* stat file */ 101163340Sdes return _ftp_stat(cd, url->doc, us); 101240975Sdes} 101341989Sdes 101441989Sdes/* 101541989Sdes * List a directory 101641989Sdes */ 101741989Sdesextern void warnx(char *, ...); 101841989Sdesstruct url_ent * 101941989SdesfetchListFTP(struct url *url, char *flags) 102041989Sdes{ 102161866Sdes warnx("fetchListFTP(): not implemented"); 102261866Sdes return NULL; 102341989Sdes} 1024