ftp.c revision 67043
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 67043 2000-10-12 22:10:26Z 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> 6460188Sdes#include <netdb.h> 6537573Sdes#include <stdarg.h> 6637535Sdes#include <stdio.h> 6737571Sdes#include <stdlib.h> 6837535Sdes#include <string.h> 6941869Sdes#include <time.h> 7037571Sdes#include <unistd.h> 7137535Sdes 7237535Sdes#include "fetch.h" 7340939Sdes#include "common.h" 7441862Sdes#include "ftperr.h" 7537535Sdes 7637535Sdes#define FTP_ANONYMOUS_USER "ftp" 7737535Sdes#define FTP_ANONYMOUS_PASSWORD "ftp" 7837535Sdes 7964883Sdes#define FTP_CONNECTION_ALREADY_OPEN 125 8037573Sdes#define FTP_OPEN_DATA_CONNECTION 150 8137573Sdes#define FTP_OK 200 8241869Sdes#define FTP_FILE_STATUS 213 8341863Sdes#define FTP_SERVICE_READY 220 8437573Sdes#define FTP_PASSIVE_MODE 227 8560737Sume#define FTP_LPASSIVE_MODE 228 8660737Sume#define FTP_EPASSIVE_MODE 229 8737573Sdes#define FTP_LOGGED_IN 230 8837573Sdes#define FTP_FILE_ACTION_OK 250 8937573Sdes#define FTP_NEED_PASSWORD 331 9037573Sdes#define FTP_NEED_ACCOUNT 332 9160188Sdes#define FTP_FILE_OK 350 9255557Sdes#define FTP_SYNTAX_ERROR 500 9363336Sdes#define FTP_PROTOCOL_ERROR 999 9437573Sdes 9540975Sdesstatic struct url cached_host; 9655557Sdesstatic int cached_socket; 9737535Sdes 9855557Sdesstatic char *last_reply; 9955557Sdesstatic size_t lr_size, lr_length; 10055557Sdesstatic int last_code; 10137571Sdes 10255557Sdes#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 10360707Sdes && isdigit(foo[2]) \ 10460707Sdes && (foo[3] == ' ' || foo[3] == '\0')) 10555557Sdes#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 10655557Sdes && isdigit(foo[2]) && foo[3] == '-') 10755557Sdes 10860737Sume/* translate IPv4 mapped IPv6 address to IPv4 address */ 10960737Sumestatic void 11060737Sumeunmappedaddr(struct sockaddr_in6 *sin6) 11160737Sume{ 11260737Sume struct sockaddr_in *sin4; 11360737Sume u_int32_t addr; 11460737Sume int port; 11560737Sume 11660737Sume if (sin6->sin6_family != AF_INET6 || 11760737Sume !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 11860737Sume return; 11960737Sume sin4 = (struct sockaddr_in *)sin6; 12060737Sume addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 12160737Sume port = sin6->sin6_port; 12260737Sume memset(sin4, 0, sizeof(struct sockaddr_in)); 12360737Sume sin4->sin_addr.s_addr = addr; 12460737Sume sin4->sin_port = port; 12560737Sume sin4->sin_family = AF_INET; 12660737Sume sin4->sin_len = sizeof(struct sockaddr_in); 12760737Sume} 12860737Sume 12937571Sdes/* 13055557Sdes * Get server response 13137535Sdes */ 13237535Sdesstatic int 13355557Sdes_ftp_chkerr(int cd) 13437535Sdes{ 13562215Sdes if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 13662215Sdes _fetch_syserr(); 13762215Sdes return -1; 13862215Sdes } 13962215Sdes if (isftpinfo(last_reply)) { 14062215Sdes while (!isftpreply(last_reply)) { 14162215Sdes if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 14262215Sdes _fetch_syserr(); 14362215Sdes return -1; 14462215Sdes } 14562215Sdes } 14662215Sdes } 14755557Sdes 14855557Sdes while (lr_length && isspace(last_reply[lr_length-1])) 14955557Sdes lr_length--; 15055557Sdes last_reply[lr_length] = 0; 15137573Sdes 15255557Sdes if (!isftpreply(last_reply)) { 15363336Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 15437535Sdes return -1; 15537571Sdes } 15637535Sdes 15755557Sdes last_code = (last_reply[0] - '0') * 100 15855557Sdes + (last_reply[1] - '0') * 10 15955557Sdes + (last_reply[2] - '0'); 16055557Sdes 16155557Sdes return last_code; 16237535Sdes} 16337535Sdes 16437535Sdes/* 16537573Sdes * Send a command and check reply 16637535Sdes */ 16737535Sdesstatic int 16855557Sdes_ftp_cmd(int cd, char *fmt, ...) 16937535Sdes{ 17037573Sdes va_list ap; 17162982Sdes size_t len; 17255557Sdes char *msg; 17355557Sdes int r; 17437573Sdes 17537573Sdes va_start(ap, fmt); 17662982Sdes len = vasprintf(&msg, fmt, ap); 17755557Sdes va_end(ap); 17855557Sdes 17955557Sdes if (msg == NULL) { 18055557Sdes errno = ENOMEM; 18155557Sdes _fetch_syserr(); 18255557Sdes return -1; 18355557Sdes } 18462982Sdes 18562982Sdes r = _fetch_putln(cd, msg, len); 18655557Sdes free(msg); 18762982Sdes 18855557Sdes if (r == -1) { 18955557Sdes _fetch_syserr(); 19055557Sdes return -1; 19155557Sdes } 19237571Sdes 19355557Sdes return _ftp_chkerr(cd); 19437535Sdes} 19537535Sdes 19637535Sdes/* 19763340Sdes * Return a pointer to the filename part of a path 19863340Sdes */ 19963340Sdesstatic char * 20063340Sdes_ftp_filename(char *file) 20163340Sdes{ 20263340Sdes char *s; 20363340Sdes 20463340Sdes if ((s = strrchr(file, '/')) == NULL) 20563340Sdes return file; 20663340Sdes else 20763340Sdes return s + 1; 20863340Sdes} 20963340Sdes 21063340Sdes/* 21163340Sdes * Change working directory to the directory that contains the 21263340Sdes * specified file. 21363340Sdes */ 21463340Sdesstatic int 21563340Sdes_ftp_cwd(int cd, char *file) 21663340Sdes{ 21763340Sdes char *s; 21863340Sdes int e; 21963340Sdes 22063585Sdes if ((s = strrchr(file, '/')) == NULL || s == file) { 22163340Sdes e = _ftp_cmd(cd, "CWD /"); 22263340Sdes } else { 22363340Sdes e = _ftp_cmd(cd, "CWD %.*s", s - file, file); 22463340Sdes } 22563340Sdes if (e != FTP_FILE_ACTION_OK) { 22663340Sdes _ftp_seterr(e); 22763340Sdes return -1; 22863340Sdes } 22963340Sdes return 0; 23063340Sdes} 23163340Sdes 23263340Sdes/* 23363340Sdes * Request and parse file stats 23463340Sdes */ 23563340Sdesstatic int 23663340Sdes_ftp_stat(int cd, char *file, struct url_stat *us) 23763340Sdes{ 23863340Sdes char *ln, *s; 23963340Sdes struct tm tm; 24063340Sdes time_t t; 24163340Sdes int e; 24263340Sdes 24363392Sdes us->size = -1; 24463392Sdes us->atime = us->mtime = 0; 24563392Sdes 24663340Sdes if ((s = strrchr(file, '/')) == NULL) 24763340Sdes s = file; 24863340Sdes else 24963340Sdes ++s; 25063340Sdes 25163340Sdes if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) { 25263340Sdes _ftp_seterr(e); 25363340Sdes return -1; 25463340Sdes } 25563340Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 25663340Sdes /* nothing */ ; 25763340Sdes for (us->size = 0; *ln && isdigit(*ln); ln++) 25863340Sdes us->size = us->size * 10 + *ln - '0'; 25963340Sdes if (*ln && !isspace(*ln)) { 26063340Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 26163340Sdes return -1; 26263340Sdes } 26363847Sdes if (us->size == 0) 26463847Sdes us->size = -1; 26563340Sdes DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); 26663340Sdes 26763340Sdes if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) { 26863340Sdes _ftp_seterr(e); 26963340Sdes return -1; 27063340Sdes } 27163340Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 27263340Sdes /* nothing */ ; 27363340Sdes switch (strspn(ln, "0123456789")) { 27463340Sdes case 14: 27563340Sdes break; 27663340Sdes case 15: 27763340Sdes ln++; 27863340Sdes ln[0] = '2'; 27963340Sdes ln[1] = '0'; 28063340Sdes break; 28163340Sdes default: 28263340Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 28363340Sdes return -1; 28463340Sdes } 28563340Sdes if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 28663340Sdes &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 28763340Sdes &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { 28863340Sdes _ftp_seterr(FTP_PROTOCOL_ERROR); 28963340Sdes return -1; 29063340Sdes } 29163340Sdes tm.tm_mon--; 29263340Sdes tm.tm_year -= 1900; 29363340Sdes tm.tm_isdst = -1; 29463340Sdes t = timegm(&tm); 29563340Sdes if (t == (time_t)-1) 29663340Sdes t = time(NULL); 29763340Sdes us->mtime = t; 29863340Sdes us->atime = t; 29963340Sdes DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 30063340Sdes "%02d:%02d:%02d\033[m]\n", 30163340Sdes tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 30263340Sdes tm.tm_hour, tm.tm_min, tm.tm_sec)); 30363340Sdes return 0; 30463340Sdes} 30563340Sdes 30663340Sdes/* 30737608Sdes * Transfer file 30837535Sdes */ 30937535Sdesstatic FILE * 31060188Sdes_ftp_transfer(int cd, char *oper, char *file, 31160188Sdes char *mode, off_t offset, char *flags) 31237535Sdes{ 31360737Sume struct sockaddr_storage sin; 31460737Sume struct sockaddr_in6 *sin6; 31560737Sume struct sockaddr_in *sin4; 31655544Sdes int pasv, high, verbose; 31755544Sdes int e, sd = -1; 31855544Sdes socklen_t l; 31937573Sdes char *s; 32037573Sdes FILE *df; 32155544Sdes 32255544Sdes /* check flags */ 32355544Sdes pasv = (flags && strchr(flags, 'p')); 32455544Sdes high = (flags && strchr(flags, 'h')); 32555544Sdes verbose = (flags && strchr(flags, 'v')); 32655544Sdes 32760951Sdes /* passive mode */ 32860951Sdes if (!pasv && (s = getenv("FTP_PASSIVE_MODE")) != NULL) 32960951Sdes pasv = (strncasecmp(s, "no", 2) != 0); 33060951Sdes 33160737Sume /* find our own address, bind, and listen */ 33260737Sume l = sizeof sin; 33360737Sume if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 33460737Sume goto sysouch; 33560737Sume if (sin.ss_family == AF_INET6) 33660737Sume unmappedaddr((struct sockaddr_in6 *)&sin); 33760737Sume 33837573Sdes /* open data socket */ 33960737Sume if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 34040939Sdes _fetch_syserr(); 34137573Sdes return NULL; 34237573Sdes } 34337573Sdes 34437573Sdes if (pasv) { 34560737Sume u_char addr[64]; 34637573Sdes char *ln, *p; 34737573Sdes int i; 34860737Sume int port; 34937573Sdes 35037573Sdes /* send PASV command */ 35155544Sdes if (verbose) 35255544Sdes _fetch_info("setting passive mode"); 35360737Sume switch (sin.ss_family) { 35460737Sume case AF_INET: 35560737Sume if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 35660737Sume goto ouch; 35760737Sume break; 35860737Sume case AF_INET6: 35960737Sume if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 36060737Sume if (e == -1) 36160737Sume goto ouch; 36260737Sume if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 36360737Sume goto ouch; 36460737Sume } 36560737Sume break; 36660737Sume default: 36763336Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 36837573Sdes goto ouch; 36960737Sume } 37037573Sdes 37155544Sdes /* 37255544Sdes * Find address and port number. The reply to the PASV command 37355544Sdes * is IMHO the one and only weak point in the FTP protocol. 37455544Sdes */ 37555557Sdes ln = last_reply; 37662888Sume switch (e) { 37760737Sume case FTP_PASSIVE_MODE: 37860737Sume case FTP_LPASSIVE_MODE: 37962888Sume for (p = ln + 3; *p && !isdigit(*p); p++) 38062888Sume /* nothing */ ; 38162888Sume if (!*p) { 38263336Sdes e = FTP_PROTOCOL_ERROR; 38362888Sume goto ouch; 38462888Sume } 38560737Sume l = (e == FTP_PASSIVE_MODE ? 6 : 21); 38660737Sume for (i = 0; *p && i < l; i++, p++) 38760737Sume addr[i] = strtol(p, &p, 10); 38860737Sume if (i < l) { 38963336Sdes e = FTP_PROTOCOL_ERROR; 39060737Sume goto ouch; 39160737Sume } 39260737Sume break; 39360737Sume case FTP_EPASSIVE_MODE: 39462888Sume for (p = ln + 3; *p && *p != '('; p++) 39562888Sume /* nothing */ ; 39662888Sume if (!*p) { 39763336Sdes e = FTP_PROTOCOL_ERROR; 39862888Sume goto ouch; 39962888Sume } 40062888Sume ++p; 40160737Sume if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 40260737Sume &port, &addr[3]) != 5 || 40360737Sume addr[0] != addr[1] || 40460737Sume addr[0] != addr[2] || addr[0] != addr[3]) { 40563336Sdes e = FTP_PROTOCOL_ERROR; 40660737Sume goto ouch; 40760737Sume } 40860737Sume break; 40960737Sume } 41037573Sdes 41160188Sdes /* seek to required offset */ 41260188Sdes if (offset) 41360188Sdes if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 41460188Sdes goto sysouch; 41560188Sdes 41637573Sdes /* construct sockaddr for data socket */ 41760188Sdes l = sizeof sin; 41855557Sdes if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 41937573Sdes goto sysouch; 42060737Sume if (sin.ss_family == AF_INET6) 42160737Sume unmappedaddr((struct sockaddr_in6 *)&sin); 42260737Sume switch (sin.ss_family) { 42360737Sume case AF_INET6: 42460737Sume sin6 = (struct sockaddr_in6 *)&sin; 42560737Sume if (e == FTP_EPASSIVE_MODE) 42660737Sume sin6->sin6_port = htons(port); 42760737Sume else { 42860737Sume bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 42960737Sume bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 43060737Sume } 43160737Sume break; 43260737Sume case AF_INET: 43360737Sume sin4 = (struct sockaddr_in *)&sin; 43460737Sume if (e == FTP_EPASSIVE_MODE) 43560737Sume sin4->sin_port = htons(port); 43660737Sume else { 43760737Sume bcopy(addr, (char *)&sin4->sin_addr, 4); 43860737Sume bcopy(addr + 4, (char *)&sin4->sin_port, 2); 43960737Sume } 44060737Sume break; 44160737Sume default: 44263336Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 44360737Sume break; 44460737Sume } 44537573Sdes 44637573Sdes /* connect to data port */ 44755544Sdes if (verbose) 44855544Sdes _fetch_info("opening data connection"); 44960737Sume if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 45037573Sdes goto sysouch; 45160188Sdes 45237573Sdes /* make the server initiate the transfer */ 45361866Sdes if (verbose) 45461866Sdes _fetch_info("initiating transfer"); 45563340Sdes e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 45664883Sdes if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) 45737573Sdes goto ouch; 45837573Sdes 45937573Sdes } else { 46037573Sdes u_int32_t a; 46137573Sdes u_short p; 46255544Sdes int arg, d; 46360737Sume char *ap; 46460737Sume char hname[INET6_ADDRSTRLEN]; 46537573Sdes 46660737Sume switch (sin.ss_family) { 46760737Sume case AF_INET6: 46860737Sume ((struct sockaddr_in6 *)&sin)->sin6_port = 0; 46960737Sume#ifdef IPV6_PORTRANGE 47060737Sume arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT; 47160737Sume if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 47260737Sume (char *)&arg, sizeof(arg)) == -1) 47360737Sume goto sysouch; 47460737Sume#endif 47560737Sume break; 47660737Sume case AF_INET: 47760737Sume ((struct sockaddr_in *)&sin)->sin_port = 0; 47860737Sume arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; 47960737Sume if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 48060737Sume (char *)&arg, sizeof arg) == -1) 48160737Sume goto sysouch; 48260737Sume break; 48360737Sume } 48455544Sdes if (verbose) 48555544Sdes _fetch_info("binding data socket"); 48660737Sume if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 48737573Sdes goto sysouch; 48838394Sdes if (listen(sd, 1) == -1) 48937573Sdes goto sysouch; 49037573Sdes 49137573Sdes /* find what port we're on and tell the server */ 49238394Sdes if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 49337573Sdes goto sysouch; 49460737Sume switch (sin.ss_family) { 49560737Sume case AF_INET: 49660737Sume sin4 = (struct sockaddr_in *)&sin; 49760737Sume a = ntohl(sin4->sin_addr.s_addr); 49860737Sume p = ntohs(sin4->sin_port); 49960737Sume e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 50060737Sume (a >> 24) & 0xff, (a >> 16) & 0xff, 50160737Sume (a >> 8) & 0xff, a & 0xff, 50260737Sume (p >> 8) & 0xff, p & 0xff); 50360737Sume break; 50460737Sume case AF_INET6: 50560737Sume#define UC(b) (((int)b)&0xff) 50660737Sume e = -1; 50760737Sume sin6 = (struct sockaddr_in6 *)&sin; 50860737Sume if (getnameinfo((struct sockaddr *)&sin, sin.ss_len, 50960737Sume hname, sizeof(hname), 51060737Sume NULL, 0, NI_NUMERICHOST) == 0) { 51160737Sume e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 51260737Sume htons(sin6->sin6_port)); 51360737Sume if (e == -1) 51460737Sume goto ouch; 51560737Sume } 51660737Sume if (e != FTP_OK) { 51760737Sume ap = (char *)&sin6->sin6_addr; 51860737Sume e = _ftp_cmd(cd, 51960737Sume "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 52060737Sume 6, 16, 52160737Sume UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 52260737Sume UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 52360737Sume UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 52460737Sume UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 52560737Sume 2, 52660737Sume (ntohs(sin6->sin6_port) >> 8) & 0xff, 52760737Sume ntohs(sin6->sin6_port) & 0xff); 52860737Sume } 52960737Sume break; 53060737Sume default: 53163336Sdes e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 53260737Sume goto ouch; 53360737Sume } 53441869Sdes if (e != FTP_OK) 53537573Sdes goto ouch; 53637573Sdes 53762256Sdes /* seek to required offset */ 53862256Sdes if (offset) 53962256Sdes if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 54062256Sdes goto sysouch; 54162256Sdes 54237573Sdes /* make the server initiate the transfer */ 54355544Sdes if (verbose) 54455544Sdes _fetch_info("initiating transfer"); 54563340Sdes e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 54641869Sdes if (e != FTP_OPEN_DATA_CONNECTION) 54737573Sdes goto ouch; 54837573Sdes 54937573Sdes /* accept the incoming connection and go to town */ 55038394Sdes if ((d = accept(sd, NULL, NULL)) == -1) 55137573Sdes goto sysouch; 55237573Sdes close(sd); 55337573Sdes sd = d; 55437573Sdes } 55537573Sdes 55637608Sdes if ((df = fdopen(sd, mode)) == NULL) 55737573Sdes goto sysouch; 55837573Sdes return df; 55937573Sdes 56037573Sdessysouch: 56140939Sdes _fetch_syserr(); 56260737Sume if (sd >= 0) 56360737Sume close(sd); 56441869Sdes return NULL; 56541869Sdes 56637573Sdesouch: 56755557Sdes if (e != -1) 56855557Sdes _ftp_seterr(e); 56960737Sume if (sd >= 0) 57060737Sume close(sd); 57137535Sdes return NULL; 57237535Sdes} 57337535Sdes 57437571Sdes/* 57563842Sdes * Return default port 57663842Sdes */ 57763842Sdesstatic int 57863842Sdes_ftp_default_port(void) 57963842Sdes{ 58063842Sdes struct servent *se; 58163842Sdes 58267043Sdes if ((se = getservbyname(SCHEME_FTP, "tcp")) != NULL) 58363842Sdes return ntohs(se->s_port); 58463842Sdes return FTP_DEFAULT_PORT; 58563842Sdes} 58663842Sdes 58763842Sdes/* 58837571Sdes * Log on to FTP server 58937535Sdes */ 59055557Sdesstatic int 59167043Sdes_ftp_connect(struct url *url, struct url *purl, char *flags) 59237571Sdes{ 59367043Sdes int cd, e, direct, verbose; 59460737Sume#ifdef INET6 59560737Sume int af = AF_UNSPEC; 59660737Sume#else 59760737Sume int af = AF_INET; 59860737Sume#endif 59960791Sume const char *logname; 60067043Sdes char *user, *pwd; 60160791Sume char localhost[MAXHOSTNAMELEN]; 60260791Sume char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 60337571Sdes 60455544Sdes direct = (flags && strchr(flags, 'd')); 60555544Sdes verbose = (flags && strchr(flags, 'v')); 60660737Sume if ((flags && strchr(flags, '4'))) 60760737Sume af = AF_INET; 60860737Sume else if ((flags && strchr(flags, '6'))) 60960737Sume af = AF_INET6; 61060737Sume 61167043Sdes if (direct) 61267043Sdes purl = NULL; 61367043Sdes 61437608Sdes /* check for proxy */ 61567043Sdes if (purl) { 61667043Sdes /* XXX proxy authentication! */ 61767043Sdes cd = _fetch_connect(purl->host, purl->port, af, verbose); 61837608Sdes } else { 61937608Sdes /* no proxy, go straight to target */ 62067043Sdes cd = _fetch_connect(url->host, url->port, af, verbose); 62167043Sdes purl = NULL; 62237608Sdes } 62337608Sdes 62437608Sdes /* check connection */ 62555557Sdes if (cd == -1) { 62640939Sdes _fetch_syserr(); 62737571Sdes return NULL; 62837571Sdes } 62937608Sdes 63037571Sdes /* expect welcome message */ 63155557Sdes if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 63237571Sdes goto fouch; 63367043Sdes 63467043Sdes /* XXX FTP_AUTH, and maybe .netrc */ 63537571Sdes 63637571Sdes /* send user name and password */ 63767043Sdes user = url->user; 63837608Sdes if (!user || !*user) 63937608Sdes user = FTP_ANONYMOUS_USER; 64067043Sdes if (purl && url->port == FTP_DEFAULT_PORT) 64167043Sdes e = _ftp_cmd(cd, "USER %s@%s", url->user, url->host); 64267043Sdes else if (purl) 64367043Sdes e = _ftp_cmd(cd, "USER %s@%s@%d", url->user, url->host, url->port); 64463712Sdes else 64567043Sdes e = _ftp_cmd(cd, "USER %s", url->user); 64637608Sdes 64737608Sdes /* did the server request a password? */ 64837608Sdes if (e == FTP_NEED_PASSWORD) { 64967043Sdes pwd = url->pwd; 65037608Sdes if (!pwd || !*pwd) 65160791Sume pwd = getenv("FTP_PASSWORD"); 65260791Sume if (!pwd || !*pwd) { 65360791Sume if ((logname = getlogin()) == 0) 65460791Sume logname = FTP_ANONYMOUS_PASSWORD; 65560791Sume gethostname(localhost, sizeof localhost); 65660791Sume snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost); 65760791Sume pwd = pbuf; 65860791Sume } 65955557Sdes e = _ftp_cmd(cd, "PASS %s", pwd); 66037608Sdes } 66137608Sdes 66237608Sdes /* did the server request an account? */ 66341869Sdes if (e == FTP_NEED_ACCOUNT) 66441863Sdes goto fouch; 66537608Sdes 66637608Sdes /* we should be done by now */ 66741869Sdes if (e != FTP_LOGGED_IN) 66837571Sdes goto fouch; 66937571Sdes 67037571Sdes /* might as well select mode and type at once */ 67137571Sdes#ifdef FTP_FORCE_STREAM_MODE 67255557Sdes if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 67341869Sdes goto fouch; 67437571Sdes#endif 67555557Sdes if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 67641869Sdes goto fouch; 67737571Sdes 67837571Sdes /* done */ 67955557Sdes return cd; 68037571Sdes 68137571Sdesfouch: 68255557Sdes if (e != -1) 68355557Sdes _ftp_seterr(e); 68455557Sdes close(cd); 68537571Sdes return NULL; 68637571Sdes} 68737571Sdes 68837571Sdes/* 68937571Sdes * Disconnect from server 69037571Sdes */ 69137571Sdesstatic void 69255557Sdes_ftp_disconnect(int cd) 69337571Sdes{ 69455557Sdes (void)_ftp_cmd(cd, "QUIT"); 69555557Sdes close(cd); 69637571Sdes} 69737571Sdes 69837571Sdes/* 69937571Sdes * Check if we're already connected 70037571Sdes */ 70137571Sdesstatic int 70240975Sdes_ftp_isconnected(struct url *url) 70337571Sdes{ 70437571Sdes return (cached_socket 70537571Sdes && (strcmp(url->host, cached_host.host) == 0) 70637571Sdes && (strcmp(url->user, cached_host.user) == 0) 70737571Sdes && (strcmp(url->pwd, cached_host.pwd) == 0) 70837571Sdes && (url->port == cached_host.port)); 70937571Sdes} 71037571Sdes 71137608Sdes/* 71241869Sdes * Check the cache, reconnect if no luck 71337608Sdes */ 71455557Sdesstatic int 71567043Sdes_ftp_cached_connect(struct url *url, struct url *purl, char *flags) 71637535Sdes{ 71755557Sdes int e, cd; 71837535Sdes 71955557Sdes cd = -1; 72041869Sdes 72137571Sdes /* set default port */ 72263842Sdes if (!url->port) 72363842Sdes url->port = _ftp_default_port(); 72437535Sdes 72541863Sdes /* try to use previously cached connection */ 72655557Sdes if (_ftp_isconnected(url)) { 72755557Sdes e = _ftp_cmd(cached_socket, "NOOP"); 72855557Sdes if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 72967043Sdes return cached_socket; 73055557Sdes } 73137571Sdes 73237571Sdes /* connect to server */ 73367043Sdes if ((cd = _ftp_connect(url, purl, flags)) == -1) 73467043Sdes return -1; 73567043Sdes if (cached_socket) 73667043Sdes _ftp_disconnect(cached_socket); 73767043Sdes cached_socket = cd; 73867043Sdes memcpy(&cached_host, url, sizeof *url); 73955557Sdes return cd; 74037535Sdes} 74137535Sdes 74237571Sdes/* 74367043Sdes * Check the proxy settings 74463713Sdes */ 74567043Sdesstatic struct url * 74667043Sdes_ftp_get_proxy(void) 74763713Sdes{ 74867043Sdes struct url *purl; 74963713Sdes char *p; 75067043Sdes 75167043Sdes if (((p = getenv("FTP_PROXY")) || (p = getenv("HTTP_PROXY"))) && 75267043Sdes *p && (purl = fetchParseURL(p)) != NULL) { 75367043Sdes if (!*purl->scheme) 75467043Sdes strcpy(purl->scheme, SCHEME_FTP); 75567043Sdes if (!purl->port) 75667043Sdes purl->port = _ftp_default_port(); 75767043Sdes if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 75867043Sdes strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 75967043Sdes return purl; 76067043Sdes fetchFreeURL(purl); 76167043Sdes } 76267043Sdes return NULL; 76363713Sdes} 76463713Sdes 76563713Sdes/* 76663340Sdes * Get and stat file 76737571Sdes */ 76837535SdesFILE * 76963340SdesfetchXGetFTP(struct url *url, struct url_stat *us, char *flags) 77037608Sdes{ 77167043Sdes struct url *purl; 77255557Sdes int cd; 77363713Sdes 77467043Sdes /* get the proxy URL, and check if we should use HTTP instead */ 77567043Sdes if (!strchr(flags, 'd') && (purl = _ftp_get_proxy()) != NULL) { 77667043Sdes if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 77767043Sdes return _http_request(url, "GET", us, purl, flags); 77867043Sdes } else { 77967043Sdes purl = NULL; 78067043Sdes } 78155557Sdes 78241869Sdes /* connect to server */ 78367043Sdes cd = _ftp_cached_connect(url, purl, flags); 78467043Sdes if (purl) 78567043Sdes fetchFreeURL(purl); 78667043Sdes if (cd == NULL) 78741869Sdes return NULL; 78841869Sdes 78963340Sdes /* change directory */ 79063340Sdes if (_ftp_cwd(cd, url->doc) == -1) 79163340Sdes return NULL; 79263340Sdes 79363340Sdes /* stat file */ 79463392Sdes if (us && _ftp_stat(cd, url->doc, us) == -1 79563910Sdes && fetchLastErrCode != FETCH_PROTO 79663392Sdes && fetchLastErrCode != FETCH_UNAVAIL) 79763340Sdes return NULL; 79863340Sdes 79941869Sdes /* initiate the transfer */ 80060188Sdes return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags); 80137608Sdes} 80237608Sdes 80341869Sdes/* 80463340Sdes * Get file 80563340Sdes */ 80663340SdesFILE * 80763340SdesfetchGetFTP(struct url *url, char *flags) 80863340Sdes{ 80963340Sdes return fetchXGetFTP(url, NULL, flags); 81063340Sdes} 81163340Sdes 81263340Sdes/* 81341869Sdes * Put file 81441869Sdes */ 81537608SdesFILE * 81640975SdesfetchPutFTP(struct url *url, char *flags) 81737535Sdes{ 81867043Sdes struct url *purl; 81955557Sdes int cd; 82041869Sdes 82167043Sdes /* get the proxy URL, and check if we should use HTTP instead */ 82267043Sdes if (!strchr(flags, 'd') && (purl = _ftp_get_proxy()) != NULL) { 82367043Sdes if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 82467043Sdes /* XXX HTTP PUT is not implemented, so try without the proxy */ 82567043Sdes purl = NULL; 82667043Sdes } else { 82767043Sdes purl = NULL; 82867043Sdes } 82963713Sdes 83041869Sdes /* connect to server */ 83167043Sdes cd = _ftp_cached_connect(url, purl, flags); 83267043Sdes if (purl) 83367043Sdes fetchFreeURL(purl); 83467043Sdes if (cd == NULL) 83541869Sdes return NULL; 83641869Sdes 83763340Sdes /* change directory */ 83863340Sdes if (_ftp_cwd(cd, url->doc) == -1) 83963340Sdes return NULL; 84063340Sdes 84141869Sdes /* initiate the transfer */ 84255557Sdes return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR", 84360188Sdes url->doc, "w", url->offset, flags); 84437535Sdes} 84540975Sdes 84641869Sdes/* 84741869Sdes * Get file stats 84841869Sdes */ 84940975Sdesint 85040975SdesfetchStatFTP(struct url *url, struct url_stat *us, char *flags) 85140975Sdes{ 85267043Sdes struct url *purl; 85363340Sdes int cd; 85441869Sdes 85567043Sdes /* get the proxy URL, and check if we should use HTTP instead */ 85667043Sdes if (!strchr(flags, 'd') && (purl = _ftp_get_proxy()) != NULL) { 85767043Sdes if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) { 85867043Sdes FILE *f; 85967043Sdes 86067043Sdes if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL) 86167043Sdes return -1; 86267043Sdes fclose(f); 86367043Sdes return 0; 86467043Sdes } 86567043Sdes } else { 86667043Sdes purl = NULL; 86767043Sdes } 86863713Sdes 86941869Sdes /* connect to server */ 87067043Sdes cd = _ftp_cached_connect(url, purl, flags); 87167043Sdes if (purl) 87267043Sdes fetchFreeURL(purl); 87367043Sdes if (cd == NULL) 87467043Sdes return NULL; 87567043Sdes 87641869Sdes /* change directory */ 87763340Sdes if (_ftp_cwd(cd, url->doc) == -1) 87841869Sdes return -1; 87941869Sdes 88063340Sdes /* stat file */ 88163340Sdes return _ftp_stat(cd, url->doc, us); 88240975Sdes} 88341989Sdes 88441989Sdes/* 88541989Sdes * List a directory 88641989Sdes */ 88741989Sdesextern void warnx(char *, ...); 88841989Sdesstruct url_ent * 88941989SdesfetchListFTP(struct url *url, char *flags) 89041989Sdes{ 89161866Sdes warnx("fetchListFTP(): not implemented"); 89261866Sdes return NULL; 89341989Sdes} 894