ftp.c revision 60791
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 60791 2000-05-22 13:01:13Z ume $ 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> 6055557Sdes#include <sys/uio.h> 6137535Sdes#include <netinet/in.h> 6237535Sdes 6337535Sdes#include <ctype.h> 6455557Sdes#include <errno.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" 7937573Sdes#define FTP_DEFAULT_PORT 21 8037535Sdes 8137573Sdes#define FTP_OPEN_DATA_CONNECTION 150 8237573Sdes#define FTP_OK 200 8341869Sdes#define FTP_FILE_STATUS 213 8441863Sdes#define FTP_SERVICE_READY 220 8537573Sdes#define FTP_PASSIVE_MODE 227 8660737Sume#define FTP_LPASSIVE_MODE 228 8760737Sume#define FTP_EPASSIVE_MODE 229 8837573Sdes#define FTP_LOGGED_IN 230 8937573Sdes#define FTP_FILE_ACTION_OK 250 9037573Sdes#define FTP_NEED_PASSWORD 331 9137573Sdes#define FTP_NEED_ACCOUNT 332 9260188Sdes#define FTP_FILE_OK 350 9355557Sdes#define FTP_SYNTAX_ERROR 500 9437573Sdes 9555557Sdesstatic char ENDL[2] = "\r\n"; 9637571Sdes 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{ 13737535Sdes do { 13855557Sdes if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 13940939Sdes _fetch_syserr(); 14037535Sdes return -1; 14137571Sdes } 14237573Sdes#ifndef NDEBUG 14355557Sdes _fetch_info("got reply '%.*s'", lr_length - 2, last_reply); 14437573Sdes#endif 14555557Sdes } while (isftpinfo(last_reply)); 14655557Sdes 14755557Sdes while (lr_length && isspace(last_reply[lr_length-1])) 14855557Sdes lr_length--; 14955557Sdes last_reply[lr_length] = 0; 15037573Sdes 15155557Sdes if (!isftpreply(last_reply)) { 15255557Sdes _ftp_seterr(999); 15337535Sdes return -1; 15437571Sdes } 15537535Sdes 15655557Sdes last_code = (last_reply[0] - '0') * 100 15755557Sdes + (last_reply[1] - '0') * 10 15855557Sdes + (last_reply[2] - '0'); 15955557Sdes 16055557Sdes return last_code; 16137535Sdes} 16237535Sdes 16337535Sdes/* 16437573Sdes * Send a command and check reply 16537535Sdes */ 16637535Sdesstatic int 16755557Sdes_ftp_cmd(int cd, char *fmt, ...) 16837535Sdes{ 16937573Sdes va_list ap; 17055557Sdes struct iovec iov[2]; 17155557Sdes char *msg; 17255557Sdes int r; 17337573Sdes 17437573Sdes va_start(ap, fmt); 17555557Sdes vasprintf(&msg, fmt, ap); 17655557Sdes va_end(ap); 17755557Sdes 17855557Sdes if (msg == NULL) { 17955557Sdes errno = ENOMEM; 18055557Sdes _fetch_syserr(); 18155557Sdes return -1; 18255557Sdes } 18337573Sdes#ifndef NDEBUG 18455557Sdes _fetch_info("sending '%s'", msg); 18537573Sdes#endif 18655557Sdes iov[0].iov_base = msg; 18755557Sdes iov[0].iov_len = strlen(msg); 18855557Sdes iov[1].iov_base = ENDL; 18960188Sdes iov[1].iov_len = sizeof ENDL; 19055557Sdes r = writev(cd, iov, 2); 19155557Sdes free(msg); 19255557Sdes if (r == -1) { 19355557Sdes _fetch_syserr(); 19455557Sdes return -1; 19555557Sdes } 19637571Sdes 19755557Sdes return _ftp_chkerr(cd); 19837535Sdes} 19937535Sdes 20037535Sdes/* 20137608Sdes * Transfer file 20237535Sdes */ 20337535Sdesstatic FILE * 20460188Sdes_ftp_transfer(int cd, char *oper, char *file, 20560188Sdes char *mode, off_t offset, char *flags) 20637535Sdes{ 20760737Sume struct sockaddr_storage sin; 20860737Sume struct sockaddr_in6 *sin6; 20960737Sume struct sockaddr_in *sin4; 21055544Sdes int pasv, high, verbose; 21155544Sdes int e, sd = -1; 21255544Sdes socklen_t l; 21337573Sdes char *s; 21437573Sdes FILE *df; 21555544Sdes 21655544Sdes /* check flags */ 21755544Sdes pasv = (flags && strchr(flags, 'p')); 21855544Sdes high = (flags && strchr(flags, 'h')); 21955544Sdes verbose = (flags && strchr(flags, 'v')); 22055544Sdes 22137535Sdes /* change directory */ 22237573Sdes if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 22337573Sdes *s = 0; 22455544Sdes if (verbose) 22555544Sdes _fetch_info("changing directory to %s", file); 22655557Sdes if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) { 22737573Sdes *s = '/'; 22855557Sdes if (e != -1) 22955557Sdes _ftp_seterr(e); 23037535Sdes return NULL; 23137535Sdes } 23237573Sdes *s++ = '/'; 23337535Sdes } else { 23455544Sdes if (verbose) 23555544Sdes _fetch_info("changing directory to /"); 23655557Sdes if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) { 23755557Sdes if (e != -1) 23855557Sdes _ftp_seterr(e); 23937535Sdes return NULL; 24041869Sdes } 24137535Sdes } 24237535Sdes 24337573Sdes /* s now points to file name */ 24437573Sdes 24560737Sume /* find our own address, bind, and listen */ 24660737Sume l = sizeof sin; 24760737Sume if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 24860737Sume goto sysouch; 24960737Sume if (sin.ss_family == AF_INET6) 25060737Sume unmappedaddr((struct sockaddr_in6 *)&sin); 25160737Sume 25237573Sdes /* open data socket */ 25360737Sume if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 25440939Sdes _fetch_syserr(); 25537573Sdes return NULL; 25637573Sdes } 25737573Sdes 25837573Sdes if (pasv) { 25960737Sume u_char addr[64]; 26037573Sdes char *ln, *p; 26137573Sdes int i; 26260737Sume int port; 26337573Sdes 26437573Sdes /* send PASV command */ 26555544Sdes if (verbose) 26655544Sdes _fetch_info("setting passive mode"); 26760737Sume switch (sin.ss_family) { 26860737Sume case AF_INET: 26960737Sume if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 27060737Sume goto ouch; 27160737Sume break; 27260737Sume case AF_INET6: 27360737Sume if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 27460737Sume if (e == -1) 27560737Sume goto ouch; 27660737Sume if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 27760737Sume goto ouch; 27860737Sume } 27960737Sume break; 28060737Sume default: 28160737Sume e = 999; /* XXX: error code should be prepared */ 28237573Sdes goto ouch; 28360737Sume } 28437573Sdes 28555544Sdes /* 28655544Sdes * Find address and port number. The reply to the PASV command 28755544Sdes * is IMHO the one and only weak point in the FTP protocol. 28855544Sdes */ 28955557Sdes ln = last_reply; 29060737Sume for (p = ln + 3; *p && *p != '('; p++) 29137573Sdes /* nothing */ ; 29260737Sume if (!*p) { 29360707Sdes e = 999; 29460707Sdes goto ouch; 29537573Sdes } 29660737Sume p++; 29760737Sume switch (e) { 29860737Sume case FTP_PASSIVE_MODE: 29960737Sume case FTP_LPASSIVE_MODE: 30060737Sume l = (e == FTP_PASSIVE_MODE ? 6 : 21); 30160737Sume for (i = 0; *p && i < l; i++, p++) 30260737Sume addr[i] = strtol(p, &p, 10); 30360737Sume if (i < l) { 30460737Sume e = 999; 30560737Sume goto ouch; 30660737Sume } 30760737Sume break; 30860737Sume case FTP_EPASSIVE_MODE: 30960737Sume if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 31060737Sume &port, &addr[3]) != 5 || 31160737Sume addr[0] != addr[1] || 31260737Sume addr[0] != addr[2] || addr[0] != addr[3]) { 31360737Sume e = 999; 31460737Sume goto ouch; 31560737Sume } 31660737Sume break; 31760737Sume } 31837573Sdes 31960188Sdes /* seek to required offset */ 32060188Sdes if (offset) 32160188Sdes if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 32260188Sdes goto sysouch; 32360188Sdes 32437573Sdes /* construct sockaddr for data socket */ 32560188Sdes l = sizeof sin; 32655557Sdes if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 32737573Sdes goto sysouch; 32860737Sume if (sin.ss_family == AF_INET6) 32960737Sume unmappedaddr((struct sockaddr_in6 *)&sin); 33060737Sume switch (sin.ss_family) { 33160737Sume case AF_INET6: 33260737Sume sin6 = (struct sockaddr_in6 *)&sin; 33360737Sume if (e == FTP_EPASSIVE_MODE) 33460737Sume sin6->sin6_port = htons(port); 33560737Sume else { 33660737Sume bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 33760737Sume bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 33860737Sume } 33960737Sume break; 34060737Sume case AF_INET: 34160737Sume sin4 = (struct sockaddr_in *)&sin; 34260737Sume if (e == FTP_EPASSIVE_MODE) 34360737Sume sin4->sin_port = htons(port); 34460737Sume else { 34560737Sume bcopy(addr, (char *)&sin4->sin_addr, 4); 34660737Sume bcopy(addr + 4, (char *)&sin4->sin_port, 2); 34760737Sume } 34860737Sume break; 34960737Sume default: 35060737Sume e = 999; /* XXX: error code should be prepared */ 35160737Sume break; 35260737Sume } 35337573Sdes 35437573Sdes /* connect to data port */ 35555544Sdes if (verbose) 35655544Sdes _fetch_info("opening data connection"); 35760737Sume if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 35837573Sdes goto sysouch; 35960188Sdes 36037573Sdes /* make the server initiate the transfer */ 36155544Sdes if (verbose) 36255544Sdes _fetch_info("initiating transfer"); 36355557Sdes e = _ftp_cmd(cd, "%s %s", oper, s); 36441869Sdes if (e != FTP_OPEN_DATA_CONNECTION) 36537573Sdes goto ouch; 36637573Sdes 36737573Sdes } else { 36837573Sdes u_int32_t a; 36937573Sdes u_short p; 37055544Sdes int arg, d; 37160737Sume char *ap; 37260737Sume char hname[INET6_ADDRSTRLEN]; 37337573Sdes 37460737Sume switch (sin.ss_family) { 37560737Sume case AF_INET6: 37660737Sume ((struct sockaddr_in6 *)&sin)->sin6_port = 0; 37760737Sume#ifdef IPV6_PORTRANGE 37860737Sume arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT; 37960737Sume if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 38060737Sume (char *)&arg, sizeof(arg)) == -1) 38160737Sume goto sysouch; 38260737Sume#endif 38360737Sume break; 38460737Sume case AF_INET: 38560737Sume ((struct sockaddr_in *)&sin)->sin_port = 0; 38660737Sume arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; 38760737Sume if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 38860737Sume (char *)&arg, sizeof arg) == -1) 38960737Sume goto sysouch; 39060737Sume break; 39160737Sume } 39255544Sdes if (verbose) 39355544Sdes _fetch_info("binding data socket"); 39460737Sume if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 39537573Sdes goto sysouch; 39638394Sdes if (listen(sd, 1) == -1) 39737573Sdes goto sysouch; 39837573Sdes 39937573Sdes /* find what port we're on and tell the server */ 40038394Sdes if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 40137573Sdes goto sysouch; 40260737Sume switch (sin.ss_family) { 40360737Sume case AF_INET: 40460737Sume sin4 = (struct sockaddr_in *)&sin; 40560737Sume a = ntohl(sin4->sin_addr.s_addr); 40660737Sume p = ntohs(sin4->sin_port); 40760737Sume e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 40860737Sume (a >> 24) & 0xff, (a >> 16) & 0xff, 40960737Sume (a >> 8) & 0xff, a & 0xff, 41060737Sume (p >> 8) & 0xff, p & 0xff); 41160737Sume break; 41260737Sume case AF_INET6: 41360737Sume#define UC(b) (((int)b)&0xff) 41460737Sume e = -1; 41560737Sume sin6 = (struct sockaddr_in6 *)&sin; 41660737Sume if (getnameinfo((struct sockaddr *)&sin, sin.ss_len, 41760737Sume hname, sizeof(hname), 41860737Sume NULL, 0, NI_NUMERICHOST) == 0) { 41960737Sume e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 42060737Sume htons(sin6->sin6_port)); 42160737Sume if (e == -1) 42260737Sume goto ouch; 42360737Sume } 42460737Sume if (e != FTP_OK) { 42560737Sume ap = (char *)&sin6->sin6_addr; 42660737Sume e = _ftp_cmd(cd, 42760737Sume "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 42860737Sume 6, 16, 42960737Sume UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 43060737Sume UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 43160737Sume UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 43260737Sume UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 43360737Sume 2, 43460737Sume (ntohs(sin6->sin6_port) >> 8) & 0xff, 43560737Sume ntohs(sin6->sin6_port) & 0xff); 43660737Sume } 43760737Sume break; 43860737Sume default: 43960737Sume e = 999; /* XXX: error code should be prepared */ 44060737Sume goto ouch; 44160737Sume } 44241869Sdes if (e != FTP_OK) 44337573Sdes goto ouch; 44437573Sdes 44537573Sdes /* make the server initiate the transfer */ 44655544Sdes if (verbose) 44755544Sdes _fetch_info("initiating transfer"); 44855557Sdes e = _ftp_cmd(cd, "%s %s", oper, s); 44941869Sdes if (e != FTP_OPEN_DATA_CONNECTION) 45037573Sdes goto ouch; 45137573Sdes 45237573Sdes /* accept the incoming connection and go to town */ 45338394Sdes if ((d = accept(sd, NULL, NULL)) == -1) 45437573Sdes goto sysouch; 45537573Sdes close(sd); 45637573Sdes sd = d; 45737573Sdes } 45837573Sdes 45937608Sdes if ((df = fdopen(sd, mode)) == NULL) 46037573Sdes goto sysouch; 46137573Sdes return df; 46237573Sdes 46337573Sdessysouch: 46440939Sdes _fetch_syserr(); 46560737Sume if (sd >= 0) 46660737Sume close(sd); 46741869Sdes return NULL; 46841869Sdes 46937573Sdesouch: 47055557Sdes if (e != -1) 47155557Sdes _ftp_seterr(e); 47260737Sume if (sd >= 0) 47360737Sume close(sd); 47437535Sdes return NULL; 47537535Sdes} 47637535Sdes 47737571Sdes/* 47837571Sdes * Log on to FTP server 47937535Sdes */ 48055557Sdesstatic int 48155544Sdes_ftp_connect(char *host, int port, char *user, char *pwd, char *flags) 48237571Sdes{ 48360188Sdes int cd, e, pp = 0, direct, verbose; 48460737Sume#ifdef INET6 48560737Sume int af = AF_UNSPEC; 48660737Sume#else 48760737Sume int af = AF_INET; 48860737Sume#endif 48937608Sdes char *p, *q; 49060791Sume const char *logname; 49160791Sume char localhost[MAXHOSTNAMELEN]; 49260791Sume char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 49337571Sdes 49455544Sdes direct = (flags && strchr(flags, 'd')); 49555544Sdes verbose = (flags && strchr(flags, 'v')); 49660737Sume if ((flags && strchr(flags, '4'))) 49760737Sume af = AF_INET; 49860737Sume else if ((flags && strchr(flags, '6'))) 49960737Sume af = AF_INET6; 50060737Sume 50137608Sdes /* check for proxy */ 50255544Sdes if (!direct && (p = getenv("FTP_PROXY")) != NULL) { 50360737Sume char c = 0; 50460737Sume 50560737Sume#ifdef INET6 50660737Sume if (*p != '[' || (q = strchr(p + 1, ']')) == NULL || 50760737Sume (*++q != '\0' && *q != ':')) 50860737Sume#endif 50960737Sume q = strchr(p, ':'); 51060737Sume if (q != NULL && *q == ':') { 51160188Sdes if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) { 51260188Sdes /* XXX we should emit some kind of warning */ 51360188Sdes } 51437608Sdes pp = atoi(q+1); 51560188Sdes if (pp < 1 || pp > 65535) { 51660188Sdes /* XXX we should emit some kind of warning */ 51760188Sdes } 51837608Sdes } 51960188Sdes if (!pp) { 52060188Sdes struct servent *se; 52160188Sdes 52260188Sdes if ((se = getservbyname("ftp", "tcp")) != NULL) 52360188Sdes pp = ntohs(se->s_port); 52460188Sdes else 52560188Sdes pp = FTP_DEFAULT_PORT; 52660188Sdes } 52760737Sume if (q) { 52860737Sume#ifdef INET6 52960737Sume if (q > p && *p == '[' && *(q - 1) == ']') { 53060737Sume p++; 53160737Sume q--; 53260737Sume } 53360737Sume#endif 53460737Sume c = *q; 53537608Sdes *q = 0; 53660737Sume } 53760737Sume cd = _fetch_connect(p, pp, af, verbose); 53837608Sdes if (q) 53960737Sume *q = c; 54037608Sdes } else { 54137608Sdes /* no proxy, go straight to target */ 54260737Sume cd = _fetch_connect(host, port, af, verbose); 54355544Sdes p = NULL; 54437608Sdes } 54537608Sdes 54637608Sdes /* check connection */ 54755557Sdes if (cd == -1) { 54840939Sdes _fetch_syserr(); 54937571Sdes return NULL; 55037571Sdes } 55137608Sdes 55237571Sdes /* expect welcome message */ 55355557Sdes if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 55437571Sdes goto fouch; 55537571Sdes 55637571Sdes /* send user name and password */ 55737608Sdes if (!user || !*user) 55837608Sdes user = FTP_ANONYMOUS_USER; 55955557Sdes e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port) 56055557Sdes : _ftp_cmd(cd, "USER %s", user); 56137608Sdes 56237608Sdes /* did the server request a password? */ 56337608Sdes if (e == FTP_NEED_PASSWORD) { 56437608Sdes if (!pwd || !*pwd) 56560791Sume pwd = getenv("FTP_PASSWORD"); 56660791Sume if (!pwd || !*pwd) { 56760791Sume if ((logname = getlogin()) == 0) 56860791Sume logname = FTP_ANONYMOUS_PASSWORD; 56960791Sume gethostname(localhost, sizeof localhost); 57060791Sume snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost); 57160791Sume pwd = pbuf; 57260791Sume } 57355557Sdes e = _ftp_cmd(cd, "PASS %s", pwd); 57437608Sdes } 57537608Sdes 57637608Sdes /* did the server request an account? */ 57741869Sdes if (e == FTP_NEED_ACCOUNT) 57841863Sdes goto fouch; 57937608Sdes 58037608Sdes /* we should be done by now */ 58141869Sdes if (e != FTP_LOGGED_IN) 58237571Sdes goto fouch; 58337571Sdes 58437571Sdes /* might as well select mode and type at once */ 58537571Sdes#ifdef FTP_FORCE_STREAM_MODE 58655557Sdes if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 58741869Sdes goto fouch; 58837571Sdes#endif 58955557Sdes if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 59041869Sdes goto fouch; 59137571Sdes 59237571Sdes /* done */ 59355557Sdes return cd; 59437571Sdes 59537571Sdesfouch: 59655557Sdes if (e != -1) 59755557Sdes _ftp_seterr(e); 59855557Sdes close(cd); 59937571Sdes return NULL; 60037571Sdes} 60137571Sdes 60237571Sdes/* 60337571Sdes * Disconnect from server 60437571Sdes */ 60537571Sdesstatic void 60655557Sdes_ftp_disconnect(int cd) 60737571Sdes{ 60855557Sdes (void)_ftp_cmd(cd, "QUIT"); 60955557Sdes close(cd); 61037571Sdes} 61137571Sdes 61237571Sdes/* 61337571Sdes * Check if we're already connected 61437571Sdes */ 61537571Sdesstatic int 61640975Sdes_ftp_isconnected(struct url *url) 61737571Sdes{ 61837571Sdes return (cached_socket 61937571Sdes && (strcmp(url->host, cached_host.host) == 0) 62037571Sdes && (strcmp(url->user, cached_host.user) == 0) 62137571Sdes && (strcmp(url->pwd, cached_host.pwd) == 0) 62237571Sdes && (url->port == cached_host.port)); 62337571Sdes} 62437571Sdes 62537608Sdes/* 62641869Sdes * Check the cache, reconnect if no luck 62737608Sdes */ 62855557Sdesstatic int 62941869Sdes_ftp_cached_connect(struct url *url, char *flags) 63037535Sdes{ 63155557Sdes int e, cd; 63237535Sdes 63355557Sdes cd = -1; 63441869Sdes 63537571Sdes /* set default port */ 63660188Sdes if (!url->port) { 63760188Sdes struct servent *se; 63860188Sdes 63960188Sdes if ((se = getservbyname("ftp", "tcp")) != NULL) 64060188Sdes url->port = ntohs(se->s_port); 64160188Sdes else 64260188Sdes url->port = FTP_DEFAULT_PORT; 64360188Sdes } 64437535Sdes 64541863Sdes /* try to use previously cached connection */ 64655557Sdes if (_ftp_isconnected(url)) { 64755557Sdes e = _ftp_cmd(cached_socket, "NOOP"); 64855557Sdes if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 64955557Sdes cd = cached_socket; 65055557Sdes } 65137571Sdes 65237571Sdes /* connect to server */ 65355557Sdes if (cd == -1) { 65455557Sdes cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags); 65555557Sdes if (cd == -1) 65655557Sdes return -1; 65737571Sdes if (cached_socket) 65837571Sdes _ftp_disconnect(cached_socket); 65955557Sdes cached_socket = cd; 66060188Sdes memcpy(&cached_host, url, sizeof *url); 66137535Sdes } 66237571Sdes 66355557Sdes return cd; 66437535Sdes} 66537535Sdes 66637571Sdes/* 66741869Sdes * Get file 66837571Sdes */ 66937535SdesFILE * 67040975SdesfetchGetFTP(struct url *url, char *flags) 67137608Sdes{ 67255557Sdes int cd; 67355557Sdes 67441869Sdes /* connect to server */ 67555557Sdes if ((cd = _ftp_cached_connect(url, flags)) == NULL) 67641869Sdes return NULL; 67741869Sdes 67841869Sdes /* initiate the transfer */ 67960188Sdes return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags); 68037608Sdes} 68137608Sdes 68241869Sdes/* 68341869Sdes * Put file 68441869Sdes */ 68537608SdesFILE * 68640975SdesfetchPutFTP(struct url *url, char *flags) 68737535Sdes{ 68855557Sdes int cd; 68941869Sdes 69041869Sdes /* connect to server */ 69155557Sdes if ((cd = _ftp_cached_connect(url, flags)) == NULL) 69241869Sdes return NULL; 69341869Sdes 69441869Sdes /* initiate the transfer */ 69555557Sdes return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR", 69660188Sdes url->doc, "w", url->offset, flags); 69737535Sdes} 69840975Sdes 69941869Sdes/* 70041869Sdes * Get file stats 70141869Sdes */ 70240975Sdesint 70340975SdesfetchStatFTP(struct url *url, struct url_stat *us, char *flags) 70440975Sdes{ 70541869Sdes char *ln, *s; 70641869Sdes struct tm tm; 70741869Sdes time_t t; 70855557Sdes int e, cd; 70941869Sdes 71060582Sdes us->size = -1; 71160582Sdes us->atime = us->mtime = 0; 71260582Sdes 71341869Sdes /* connect to server */ 71455557Sdes if ((cd = _ftp_cached_connect(url, flags)) == NULL) 71541869Sdes return -1; 71641869Sdes 71741869Sdes /* change directory */ 71841869Sdes if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) { 71941869Sdes *s = 0; 72055557Sdes if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) { 72141869Sdes *s = '/'; 72241869Sdes goto ouch; 72341869Sdes } 72441869Sdes *s++ = '/'; 72541869Sdes } else { 72655557Sdes if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) 72741869Sdes goto ouch; 72841869Sdes } 72941869Sdes 73041869Sdes /* s now points to file name */ 73141869Sdes 73255557Sdes if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS) 73341869Sdes goto ouch; 73455557Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 73541869Sdes /* nothing */ ; 73641869Sdes for (us->size = 0; *ln && isdigit(*ln); ln++) 73741869Sdes us->size = us->size * 10 + *ln - '0'; 73841869Sdes if (*ln && !isspace(*ln)) { 73955557Sdes _ftp_seterr(999); 74041869Sdes return -1; 74141869Sdes } 74260383Sdes DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); 74341869Sdes 74455557Sdes if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) 74541869Sdes goto ouch; 74655557Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 74741869Sdes /* nothing */ ; 74860383Sdes e = 999; 74960383Sdes switch (strspn(ln, "0123456789")) { 75060383Sdes case 14: 75160383Sdes break; 75260383Sdes case 15: 75360383Sdes ln++; 75460383Sdes ln[0] = '2'; 75560383Sdes ln[1] = '0'; 75660383Sdes break; 75760383Sdes default: 75860383Sdes goto ouch; 75960383Sdes } 76060383Sdes if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 76160383Sdes &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 76260383Sdes &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) 76360383Sdes goto ouch; 76441869Sdes tm.tm_mon--; 76541869Sdes tm.tm_year -= 1900; 76641869Sdes tm.tm_isdst = -1; 76760383Sdes t = timegm(&tm); 76856635Sdes if (t == (time_t)-1) 76956635Sdes t = time(NULL); 77056635Sdes us->mtime = t; 77156635Sdes us->atime = t; 77260383Sdes DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 77360383Sdes "%02d:%02d:%02d\033[m]\n", 77460383Sdes tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 77560383Sdes tm.tm_hour, tm.tm_min, tm.tm_sec)); 77641869Sdes return 0; 77741869Sdes 77841869Sdesouch: 77955557Sdes if (e != -1) 78055557Sdes _ftp_seterr(e); 78140975Sdes return -1; 78240975Sdes} 78341989Sdes 78441989Sdes/* 78541989Sdes * List a directory 78641989Sdes */ 78741989Sdesextern void warnx(char *, ...); 78841989Sdesstruct url_ent * 78941989SdesfetchListFTP(struct url *url, char *flags) 79041989Sdes{ 79141989Sdes warnx("fetchListFTP(): not implemented"); 79241989Sdes return NULL; 79341989Sdes} 794