ftp.c revision 62256
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 62256 2000-06-29 10:44:10Z 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> 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{ 13762215Sdes if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 13862215Sdes _fetch_syserr(); 13962215Sdes return -1; 14062215Sdes } 14137573Sdes#ifndef NDEBUG 14262215Sdes _fetch_info("got reply '%.*s'", lr_length - 2, last_reply); 14337573Sdes#endif 14462215Sdes if (isftpinfo(last_reply)) { 14562215Sdes while (!isftpreply(last_reply)) { 14662215Sdes if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 14762215Sdes _fetch_syserr(); 14862215Sdes return -1; 14962215Sdes } 15062215Sdes#ifndef NDEBUG 15162215Sdes _fetch_info("got reply '%.*s'", lr_length - 2, last_reply); 15262215Sdes#endif 15362215Sdes } 15462215Sdes } 15555557Sdes 15655557Sdes while (lr_length && isspace(last_reply[lr_length-1])) 15755557Sdes lr_length--; 15855557Sdes last_reply[lr_length] = 0; 15937573Sdes 16055557Sdes if (!isftpreply(last_reply)) { 16155557Sdes _ftp_seterr(999); 16237535Sdes return -1; 16337571Sdes } 16437535Sdes 16555557Sdes last_code = (last_reply[0] - '0') * 100 16655557Sdes + (last_reply[1] - '0') * 10 16755557Sdes + (last_reply[2] - '0'); 16855557Sdes 16955557Sdes return last_code; 17037535Sdes} 17137535Sdes 17237535Sdes/* 17337573Sdes * Send a command and check reply 17437535Sdes */ 17537535Sdesstatic int 17655557Sdes_ftp_cmd(int cd, char *fmt, ...) 17737535Sdes{ 17837573Sdes va_list ap; 17955557Sdes struct iovec iov[2]; 18055557Sdes char *msg; 18155557Sdes int r; 18237573Sdes 18337573Sdes va_start(ap, fmt); 18455557Sdes vasprintf(&msg, fmt, ap); 18555557Sdes va_end(ap); 18655557Sdes 18755557Sdes if (msg == NULL) { 18855557Sdes errno = ENOMEM; 18955557Sdes _fetch_syserr(); 19055557Sdes return -1; 19155557Sdes } 19237573Sdes#ifndef NDEBUG 19355557Sdes _fetch_info("sending '%s'", msg); 19437573Sdes#endif 19555557Sdes iov[0].iov_base = msg; 19655557Sdes iov[0].iov_len = strlen(msg); 19755557Sdes iov[1].iov_base = ENDL; 19860188Sdes iov[1].iov_len = sizeof ENDL; 19955557Sdes r = writev(cd, iov, 2); 20055557Sdes free(msg); 20155557Sdes if (r == -1) { 20255557Sdes _fetch_syserr(); 20355557Sdes return -1; 20455557Sdes } 20537571Sdes 20655557Sdes return _ftp_chkerr(cd); 20737535Sdes} 20837535Sdes 20937535Sdes/* 21037608Sdes * Transfer file 21137535Sdes */ 21237535Sdesstatic FILE * 21360188Sdes_ftp_transfer(int cd, char *oper, char *file, 21460188Sdes char *mode, off_t offset, char *flags) 21537535Sdes{ 21660737Sume struct sockaddr_storage sin; 21760737Sume struct sockaddr_in6 *sin6; 21860737Sume struct sockaddr_in *sin4; 21955544Sdes int pasv, high, verbose; 22055544Sdes int e, sd = -1; 22155544Sdes socklen_t l; 22237573Sdes char *s; 22337573Sdes FILE *df; 22455544Sdes 22555544Sdes /* check flags */ 22655544Sdes pasv = (flags && strchr(flags, 'p')); 22755544Sdes high = (flags && strchr(flags, 'h')); 22855544Sdes verbose = (flags && strchr(flags, 'v')); 22955544Sdes 23060951Sdes /* passive mode */ 23160951Sdes if (!pasv && (s = getenv("FTP_PASSIVE_MODE")) != NULL) 23260951Sdes pasv = (strncasecmp(s, "no", 2) != 0); 23360951Sdes 23437535Sdes /* change directory */ 23537573Sdes if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 23637573Sdes *s = 0; 23755544Sdes if (verbose) 23855544Sdes _fetch_info("changing directory to %s", file); 23955557Sdes if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) { 24037573Sdes *s = '/'; 24155557Sdes if (e != -1) 24255557Sdes _ftp_seterr(e); 24337535Sdes return NULL; 24437535Sdes } 24537573Sdes *s++ = '/'; 24637535Sdes } else { 24755544Sdes if (verbose) 24855544Sdes _fetch_info("changing directory to /"); 24955557Sdes if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) { 25055557Sdes if (e != -1) 25155557Sdes _ftp_seterr(e); 25237535Sdes return NULL; 25341869Sdes } 25437535Sdes } 25537535Sdes 25637573Sdes /* s now points to file name */ 25737573Sdes 25860737Sume /* find our own address, bind, and listen */ 25960737Sume l = sizeof sin; 26060737Sume if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 26160737Sume goto sysouch; 26260737Sume if (sin.ss_family == AF_INET6) 26360737Sume unmappedaddr((struct sockaddr_in6 *)&sin); 26460737Sume 26537573Sdes /* open data socket */ 26660737Sume if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 26740939Sdes _fetch_syserr(); 26837573Sdes return NULL; 26937573Sdes } 27037573Sdes 27137573Sdes if (pasv) { 27260737Sume u_char addr[64]; 27337573Sdes char *ln, *p; 27437573Sdes int i; 27560737Sume int port; 27637573Sdes 27737573Sdes /* send PASV command */ 27855544Sdes if (verbose) 27955544Sdes _fetch_info("setting passive mode"); 28060737Sume switch (sin.ss_family) { 28160737Sume case AF_INET: 28260737Sume if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 28360737Sume goto ouch; 28460737Sume break; 28560737Sume case AF_INET6: 28660737Sume if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 28760737Sume if (e == -1) 28860737Sume goto ouch; 28960737Sume if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 29060737Sume goto ouch; 29160737Sume } 29260737Sume break; 29360737Sume default: 29460737Sume e = 999; /* XXX: error code should be prepared */ 29537573Sdes goto ouch; 29660737Sume } 29737573Sdes 29855544Sdes /* 29955544Sdes * Find address and port number. The reply to the PASV command 30055544Sdes * is IMHO the one and only weak point in the FTP protocol. 30155544Sdes */ 30255557Sdes ln = last_reply; 30360737Sume for (p = ln + 3; *p && *p != '('; p++) 30437573Sdes /* nothing */ ; 30560737Sume if (!*p) { 30660707Sdes e = 999; 30760707Sdes goto ouch; 30837573Sdes } 30960737Sume p++; 31060737Sume switch (e) { 31160737Sume case FTP_PASSIVE_MODE: 31260737Sume case FTP_LPASSIVE_MODE: 31360737Sume l = (e == FTP_PASSIVE_MODE ? 6 : 21); 31460737Sume for (i = 0; *p && i < l; i++, p++) 31560737Sume addr[i] = strtol(p, &p, 10); 31660737Sume if (i < l) { 31760737Sume e = 999; 31860737Sume goto ouch; 31960737Sume } 32060737Sume break; 32160737Sume case FTP_EPASSIVE_MODE: 32260737Sume if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 32360737Sume &port, &addr[3]) != 5 || 32460737Sume addr[0] != addr[1] || 32560737Sume addr[0] != addr[2] || addr[0] != addr[3]) { 32660737Sume e = 999; 32760737Sume goto ouch; 32860737Sume } 32960737Sume break; 33060737Sume } 33137573Sdes 33260188Sdes /* seek to required offset */ 33360188Sdes if (offset) 33460188Sdes if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 33560188Sdes goto sysouch; 33660188Sdes 33737573Sdes /* construct sockaddr for data socket */ 33860188Sdes l = sizeof sin; 33955557Sdes if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 34037573Sdes goto sysouch; 34160737Sume if (sin.ss_family == AF_INET6) 34260737Sume unmappedaddr((struct sockaddr_in6 *)&sin); 34360737Sume switch (sin.ss_family) { 34460737Sume case AF_INET6: 34560737Sume sin6 = (struct sockaddr_in6 *)&sin; 34660737Sume if (e == FTP_EPASSIVE_MODE) 34760737Sume sin6->sin6_port = htons(port); 34860737Sume else { 34960737Sume bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 35060737Sume bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 35160737Sume } 35260737Sume break; 35360737Sume case AF_INET: 35460737Sume sin4 = (struct sockaddr_in *)&sin; 35560737Sume if (e == FTP_EPASSIVE_MODE) 35660737Sume sin4->sin_port = htons(port); 35760737Sume else { 35860737Sume bcopy(addr, (char *)&sin4->sin_addr, 4); 35960737Sume bcopy(addr + 4, (char *)&sin4->sin_port, 2); 36060737Sume } 36160737Sume break; 36260737Sume default: 36360737Sume e = 999; /* XXX: error code should be prepared */ 36460737Sume break; 36560737Sume } 36637573Sdes 36737573Sdes /* connect to data port */ 36855544Sdes if (verbose) 36955544Sdes _fetch_info("opening data connection"); 37060737Sume if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 37137573Sdes goto sysouch; 37260188Sdes 37337573Sdes /* make the server initiate the transfer */ 37461866Sdes if (verbose) 37561866Sdes _fetch_info("initiating transfer"); 37661866Sdes e = _ftp_cmd(cd, "%s %s", oper, s); 37741869Sdes if (e != FTP_OPEN_DATA_CONNECTION) 37837573Sdes goto ouch; 37937573Sdes 38037573Sdes } else { 38137573Sdes u_int32_t a; 38237573Sdes u_short p; 38355544Sdes int arg, d; 38460737Sume char *ap; 38560737Sume char hname[INET6_ADDRSTRLEN]; 38637573Sdes 38760737Sume switch (sin.ss_family) { 38860737Sume case AF_INET6: 38960737Sume ((struct sockaddr_in6 *)&sin)->sin6_port = 0; 39060737Sume#ifdef IPV6_PORTRANGE 39160737Sume arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT; 39260737Sume if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 39360737Sume (char *)&arg, sizeof(arg)) == -1) 39460737Sume goto sysouch; 39560737Sume#endif 39660737Sume break; 39760737Sume case AF_INET: 39860737Sume ((struct sockaddr_in *)&sin)->sin_port = 0; 39960737Sume arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; 40060737Sume if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 40160737Sume (char *)&arg, sizeof arg) == -1) 40260737Sume goto sysouch; 40360737Sume break; 40460737Sume } 40555544Sdes if (verbose) 40655544Sdes _fetch_info("binding data socket"); 40760737Sume if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 40837573Sdes goto sysouch; 40938394Sdes if (listen(sd, 1) == -1) 41037573Sdes goto sysouch; 41137573Sdes 41237573Sdes /* find what port we're on and tell the server */ 41338394Sdes if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 41437573Sdes goto sysouch; 41560737Sume switch (sin.ss_family) { 41660737Sume case AF_INET: 41760737Sume sin4 = (struct sockaddr_in *)&sin; 41860737Sume a = ntohl(sin4->sin_addr.s_addr); 41960737Sume p = ntohs(sin4->sin_port); 42060737Sume e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 42160737Sume (a >> 24) & 0xff, (a >> 16) & 0xff, 42260737Sume (a >> 8) & 0xff, a & 0xff, 42360737Sume (p >> 8) & 0xff, p & 0xff); 42460737Sume break; 42560737Sume case AF_INET6: 42660737Sume#define UC(b) (((int)b)&0xff) 42760737Sume e = -1; 42860737Sume sin6 = (struct sockaddr_in6 *)&sin; 42960737Sume if (getnameinfo((struct sockaddr *)&sin, sin.ss_len, 43060737Sume hname, sizeof(hname), 43160737Sume NULL, 0, NI_NUMERICHOST) == 0) { 43260737Sume e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 43360737Sume htons(sin6->sin6_port)); 43460737Sume if (e == -1) 43560737Sume goto ouch; 43660737Sume } 43760737Sume if (e != FTP_OK) { 43860737Sume ap = (char *)&sin6->sin6_addr; 43960737Sume e = _ftp_cmd(cd, 44060737Sume "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 44160737Sume 6, 16, 44260737Sume UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 44360737Sume UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 44460737Sume UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 44560737Sume UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 44660737Sume 2, 44760737Sume (ntohs(sin6->sin6_port) >> 8) & 0xff, 44860737Sume ntohs(sin6->sin6_port) & 0xff); 44960737Sume } 45060737Sume break; 45160737Sume default: 45260737Sume e = 999; /* XXX: error code should be prepared */ 45360737Sume goto ouch; 45460737Sume } 45541869Sdes if (e != FTP_OK) 45637573Sdes goto ouch; 45737573Sdes 45862256Sdes /* seek to required offset */ 45962256Sdes if (offset) 46062256Sdes if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 46162256Sdes goto sysouch; 46262256Sdes 46337573Sdes /* make the server initiate the transfer */ 46455544Sdes if (verbose) 46555544Sdes _fetch_info("initiating transfer"); 46655557Sdes e = _ftp_cmd(cd, "%s %s", oper, s); 46741869Sdes if (e != FTP_OPEN_DATA_CONNECTION) 46837573Sdes goto ouch; 46937573Sdes 47037573Sdes /* accept the incoming connection and go to town */ 47138394Sdes if ((d = accept(sd, NULL, NULL)) == -1) 47237573Sdes goto sysouch; 47337573Sdes close(sd); 47437573Sdes sd = d; 47537573Sdes } 47637573Sdes 47737608Sdes if ((df = fdopen(sd, mode)) == NULL) 47837573Sdes goto sysouch; 47937573Sdes return df; 48037573Sdes 48137573Sdessysouch: 48240939Sdes _fetch_syserr(); 48360737Sume if (sd >= 0) 48460737Sume close(sd); 48541869Sdes return NULL; 48641869Sdes 48737573Sdesouch: 48855557Sdes if (e != -1) 48955557Sdes _ftp_seterr(e); 49060737Sume if (sd >= 0) 49160737Sume close(sd); 49237535Sdes return NULL; 49337535Sdes} 49437535Sdes 49537571Sdes/* 49637571Sdes * Log on to FTP server 49737535Sdes */ 49855557Sdesstatic int 49955544Sdes_ftp_connect(char *host, int port, char *user, char *pwd, char *flags) 50037571Sdes{ 50160188Sdes int cd, e, pp = 0, direct, verbose; 50260737Sume#ifdef INET6 50360737Sume int af = AF_UNSPEC; 50460737Sume#else 50560737Sume int af = AF_INET; 50660737Sume#endif 50737608Sdes char *p, *q; 50860791Sume const char *logname; 50960791Sume char localhost[MAXHOSTNAMELEN]; 51060791Sume char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 51137571Sdes 51255544Sdes direct = (flags && strchr(flags, 'd')); 51355544Sdes verbose = (flags && strchr(flags, 'v')); 51460737Sume if ((flags && strchr(flags, '4'))) 51560737Sume af = AF_INET; 51660737Sume else if ((flags && strchr(flags, '6'))) 51760737Sume af = AF_INET6; 51860737Sume 51937608Sdes /* check for proxy */ 52055544Sdes if (!direct && (p = getenv("FTP_PROXY")) != NULL) { 52160737Sume char c = 0; 52260737Sume 52360737Sume#ifdef INET6 52460737Sume if (*p != '[' || (q = strchr(p + 1, ']')) == NULL || 52560737Sume (*++q != '\0' && *q != ':')) 52660737Sume#endif 52760737Sume q = strchr(p, ':'); 52860737Sume if (q != NULL && *q == ':') { 52960188Sdes if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) { 53060188Sdes /* XXX we should emit some kind of warning */ 53160188Sdes } 53237608Sdes pp = atoi(q+1); 53360188Sdes if (pp < 1 || pp > 65535) { 53460188Sdes /* XXX we should emit some kind of warning */ 53560188Sdes } 53637608Sdes } 53760188Sdes if (!pp) { 53860188Sdes struct servent *se; 53960188Sdes 54060188Sdes if ((se = getservbyname("ftp", "tcp")) != NULL) 54160188Sdes pp = ntohs(se->s_port); 54260188Sdes else 54360188Sdes pp = FTP_DEFAULT_PORT; 54460188Sdes } 54560737Sume if (q) { 54660737Sume#ifdef INET6 54760737Sume if (q > p && *p == '[' && *(q - 1) == ']') { 54860737Sume p++; 54960737Sume q--; 55060737Sume } 55160737Sume#endif 55260737Sume c = *q; 55337608Sdes *q = 0; 55460737Sume } 55560737Sume cd = _fetch_connect(p, pp, af, verbose); 55637608Sdes if (q) 55760737Sume *q = c; 55837608Sdes } else { 55937608Sdes /* no proxy, go straight to target */ 56060737Sume cd = _fetch_connect(host, port, af, verbose); 56155544Sdes p = NULL; 56237608Sdes } 56337608Sdes 56437608Sdes /* check connection */ 56555557Sdes if (cd == -1) { 56640939Sdes _fetch_syserr(); 56737571Sdes return NULL; 56837571Sdes } 56937608Sdes 57037571Sdes /* expect welcome message */ 57155557Sdes if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 57237571Sdes goto fouch; 57337571Sdes 57437571Sdes /* send user name and password */ 57537608Sdes if (!user || !*user) 57637608Sdes user = FTP_ANONYMOUS_USER; 57755557Sdes e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port) 57855557Sdes : _ftp_cmd(cd, "USER %s", user); 57937608Sdes 58037608Sdes /* did the server request a password? */ 58137608Sdes if (e == FTP_NEED_PASSWORD) { 58237608Sdes if (!pwd || !*pwd) 58360791Sume pwd = getenv("FTP_PASSWORD"); 58460791Sume if (!pwd || !*pwd) { 58560791Sume if ((logname = getlogin()) == 0) 58660791Sume logname = FTP_ANONYMOUS_PASSWORD; 58760791Sume gethostname(localhost, sizeof localhost); 58860791Sume snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost); 58960791Sume pwd = pbuf; 59060791Sume } 59155557Sdes e = _ftp_cmd(cd, "PASS %s", pwd); 59237608Sdes } 59337608Sdes 59437608Sdes /* did the server request an account? */ 59541869Sdes if (e == FTP_NEED_ACCOUNT) 59641863Sdes goto fouch; 59737608Sdes 59837608Sdes /* we should be done by now */ 59941869Sdes if (e != FTP_LOGGED_IN) 60037571Sdes goto fouch; 60137571Sdes 60237571Sdes /* might as well select mode and type at once */ 60337571Sdes#ifdef FTP_FORCE_STREAM_MODE 60455557Sdes if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 60541869Sdes goto fouch; 60637571Sdes#endif 60755557Sdes if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 60841869Sdes goto fouch; 60937571Sdes 61037571Sdes /* done */ 61155557Sdes return cd; 61237571Sdes 61337571Sdesfouch: 61455557Sdes if (e != -1) 61555557Sdes _ftp_seterr(e); 61655557Sdes close(cd); 61737571Sdes return NULL; 61837571Sdes} 61937571Sdes 62037571Sdes/* 62137571Sdes * Disconnect from server 62237571Sdes */ 62337571Sdesstatic void 62455557Sdes_ftp_disconnect(int cd) 62537571Sdes{ 62655557Sdes (void)_ftp_cmd(cd, "QUIT"); 62755557Sdes close(cd); 62837571Sdes} 62937571Sdes 63037571Sdes/* 63137571Sdes * Check if we're already connected 63237571Sdes */ 63337571Sdesstatic int 63440975Sdes_ftp_isconnected(struct url *url) 63537571Sdes{ 63637571Sdes return (cached_socket 63737571Sdes && (strcmp(url->host, cached_host.host) == 0) 63837571Sdes && (strcmp(url->user, cached_host.user) == 0) 63937571Sdes && (strcmp(url->pwd, cached_host.pwd) == 0) 64037571Sdes && (url->port == cached_host.port)); 64137571Sdes} 64237571Sdes 64337608Sdes/* 64441869Sdes * Check the cache, reconnect if no luck 64537608Sdes */ 64655557Sdesstatic int 64741869Sdes_ftp_cached_connect(struct url *url, char *flags) 64837535Sdes{ 64955557Sdes int e, cd; 65037535Sdes 65155557Sdes cd = -1; 65241869Sdes 65337571Sdes /* set default port */ 65460188Sdes if (!url->port) { 65560188Sdes struct servent *se; 65660188Sdes 65760188Sdes if ((se = getservbyname("ftp", "tcp")) != NULL) 65860188Sdes url->port = ntohs(se->s_port); 65960188Sdes else 66060188Sdes url->port = FTP_DEFAULT_PORT; 66160188Sdes } 66237535Sdes 66341863Sdes /* try to use previously cached connection */ 66455557Sdes if (_ftp_isconnected(url)) { 66555557Sdes e = _ftp_cmd(cached_socket, "NOOP"); 66655557Sdes if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 66755557Sdes cd = cached_socket; 66855557Sdes } 66937571Sdes 67037571Sdes /* connect to server */ 67155557Sdes if (cd == -1) { 67255557Sdes cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags); 67355557Sdes if (cd == -1) 67455557Sdes return -1; 67537571Sdes if (cached_socket) 67637571Sdes _ftp_disconnect(cached_socket); 67755557Sdes cached_socket = cd; 67860188Sdes memcpy(&cached_host, url, sizeof *url); 67937535Sdes } 68037571Sdes 68155557Sdes return cd; 68237535Sdes} 68337535Sdes 68437571Sdes/* 68541869Sdes * Get file 68637571Sdes */ 68737535SdesFILE * 68840975SdesfetchGetFTP(struct url *url, char *flags) 68937608Sdes{ 69055557Sdes int cd; 69155557Sdes 69241869Sdes /* connect to server */ 69355557Sdes if ((cd = _ftp_cached_connect(url, flags)) == NULL) 69441869Sdes return NULL; 69541869Sdes 69641869Sdes /* initiate the transfer */ 69760188Sdes return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags); 69837608Sdes} 69937608Sdes 70041869Sdes/* 70141869Sdes * Put file 70241869Sdes */ 70337608SdesFILE * 70440975SdesfetchPutFTP(struct url *url, char *flags) 70537535Sdes{ 70655557Sdes int cd; 70741869Sdes 70841869Sdes /* connect to server */ 70955557Sdes if ((cd = _ftp_cached_connect(url, flags)) == NULL) 71041869Sdes return NULL; 71141869Sdes 71241869Sdes /* initiate the transfer */ 71355557Sdes return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR", 71460188Sdes url->doc, "w", url->offset, flags); 71537535Sdes} 71640975Sdes 71741869Sdes/* 71841869Sdes * Get file stats 71941869Sdes */ 72040975Sdesint 72140975SdesfetchStatFTP(struct url *url, struct url_stat *us, char *flags) 72240975Sdes{ 72341869Sdes char *ln, *s; 72441869Sdes struct tm tm; 72541869Sdes time_t t; 72655557Sdes int e, cd; 72741869Sdes 72860582Sdes us->size = -1; 72960582Sdes us->atime = us->mtime = 0; 73060582Sdes 73141869Sdes /* connect to server */ 73255557Sdes if ((cd = _ftp_cached_connect(url, flags)) == NULL) 73341869Sdes return -1; 73441869Sdes 73541869Sdes /* change directory */ 73641869Sdes if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) { 73741869Sdes *s = 0; 73855557Sdes if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) { 73941869Sdes *s = '/'; 74041869Sdes goto ouch; 74141869Sdes } 74241869Sdes *s++ = '/'; 74341869Sdes } else { 74455557Sdes if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) 74541869Sdes goto ouch; 74641869Sdes } 74741869Sdes 74841869Sdes /* s now points to file name */ 74941869Sdes 75055557Sdes if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS) 75141869Sdes goto ouch; 75255557Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 75341869Sdes /* nothing */ ; 75441869Sdes for (us->size = 0; *ln && isdigit(*ln); ln++) 75541869Sdes us->size = us->size * 10 + *ln - '0'; 75641869Sdes if (*ln && !isspace(*ln)) { 75755557Sdes _ftp_seterr(999); 75841869Sdes return -1; 75941869Sdes } 76060383Sdes DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); 76141869Sdes 76255557Sdes if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) 76341869Sdes goto ouch; 76455557Sdes for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 76541869Sdes /* nothing */ ; 76660383Sdes e = 999; 76760383Sdes switch (strspn(ln, "0123456789")) { 76860383Sdes case 14: 76960383Sdes break; 77060383Sdes case 15: 77160383Sdes ln++; 77260383Sdes ln[0] = '2'; 77360383Sdes ln[1] = '0'; 77460383Sdes break; 77560383Sdes default: 77660383Sdes goto ouch; 77760383Sdes } 77860383Sdes if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 77960383Sdes &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 78060383Sdes &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) 78160383Sdes goto ouch; 78241869Sdes tm.tm_mon--; 78341869Sdes tm.tm_year -= 1900; 78441869Sdes tm.tm_isdst = -1; 78560383Sdes t = timegm(&tm); 78656635Sdes if (t == (time_t)-1) 78756635Sdes t = time(NULL); 78856635Sdes us->mtime = t; 78956635Sdes us->atime = t; 79060383Sdes DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 79160383Sdes "%02d:%02d:%02d\033[m]\n", 79260383Sdes tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 79360383Sdes tm.tm_hour, tm.tm_min, tm.tm_sec)); 79441869Sdes return 0; 79541869Sdes 79641869Sdesouch: 79755557Sdes if (e != -1) 79855557Sdes _ftp_seterr(e); 79940975Sdes return -1; 80040975Sdes} 80141989Sdes 80241989Sdes/* 80341989Sdes * List a directory 80441989Sdes */ 80541989Sdesextern void warnx(char *, ...); 80641989Sdesstruct url_ent * 80741989SdesfetchListFTP(struct url *url, char *flags) 80841989Sdes{ 80961866Sdes warnx("fetchListFTP(): not implemented"); 81061866Sdes return NULL; 81141989Sdes} 812