ftp.c revision 41989
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 * 2841989Sdes * $Id: ftp.c,v 1.11 1998/12/18 14:32:48 des Exp $ 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> 6337573Sdes#include <stdarg.h> 6437535Sdes#include <stdio.h> 6537571Sdes#include <stdlib.h> 6637535Sdes#include <string.h> 6741869Sdes#include <time.h> 6837571Sdes#include <unistd.h> 6937535Sdes 7037535Sdes#include "fetch.h" 7140939Sdes#include "common.h" 7241862Sdes#include "ftperr.h" 7337535Sdes 7437535Sdes#define FTP_ANONYMOUS_USER "ftp" 7537535Sdes#define FTP_ANONYMOUS_PASSWORD "ftp" 7637573Sdes#define FTP_DEFAULT_PORT 21 7737535Sdes 7837573Sdes#define FTP_OPEN_DATA_CONNECTION 150 7937573Sdes#define FTP_OK 200 8041869Sdes#define FTP_FILE_STATUS 213 8141863Sdes#define FTP_SERVICE_READY 220 8237573Sdes#define FTP_PASSIVE_MODE 227 8337573Sdes#define FTP_LOGGED_IN 230 8437573Sdes#define FTP_FILE_ACTION_OK 250 8537573Sdes#define FTP_NEED_PASSWORD 331 8637573Sdes#define FTP_NEED_ACCOUNT 332 8737573Sdes 8837571Sdes#define ENDL "\r\n" 8937571Sdes 9040975Sdesstatic struct url cached_host; 9137535Sdesstatic FILE *cached_socket; 9237535Sdes 9341869Sdesstatic char _ftp_last_reply[1024]; 9437571Sdes 9537571Sdes/* 9637535Sdes * Get server response, check that first digit is a '2' 9737535Sdes */ 9837535Sdesstatic int 9941863Sdes_ftp_chkerr(FILE *s) 10037535Sdes{ 10137535Sdes char *line; 10237535Sdes size_t len; 10337535Sdes 10437535Sdes do { 10537573Sdes if (((line = fgetln(s, &len)) == NULL) || (len < 4)) { 10640939Sdes _fetch_syserr(); 10737535Sdes return -1; 10837571Sdes } 10941869Sdes } while (len >= 4 && line[3] == '-'); 11037573Sdes 11141869Sdes while (len && isspace(line[len-1])) 11241869Sdes len--; 11341869Sdes snprintf(_ftp_last_reply, sizeof(_ftp_last_reply), 11441869Sdes "%*.*s", (int)len, (int)len, line); 11537535Sdes 11637573Sdes#ifndef NDEBUG 11737573Sdes fprintf(stderr, "\033[1m<<< "); 11841869Sdes fprintf(stderr, "%*.*s\n", (int)len, (int)len, line); 11937573Sdes fprintf(stderr, "\033[m"); 12037573Sdes#endif 12137573Sdes 12241869Sdes if (len < 4 || !isdigit(line[1]) || !isdigit(line[1]) 12337571Sdes || !isdigit(line[2]) || (line[3] != ' ')) { 12437535Sdes return -1; 12537571Sdes } 12637535Sdes 12741863Sdes return (line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0'); 12837535Sdes} 12937535Sdes 13037535Sdes/* 13137573Sdes * Send a command and check reply 13237535Sdes */ 13337535Sdesstatic int 13437573Sdes_ftp_cmd(FILE *f, char *fmt, ...) 13537535Sdes{ 13637573Sdes va_list ap; 13737573Sdes 13837573Sdes va_start(ap, fmt); 13937573Sdes vfprintf(f, fmt, ap); 14037573Sdes#ifndef NDEBUG 14137573Sdes fprintf(stderr, "\033[1m>>> "); 14237573Sdes vfprintf(stderr, fmt, ap); 14337573Sdes fprintf(stderr, "\033[m"); 14437573Sdes#endif 14537573Sdes va_end(ap); 14637571Sdes 14741863Sdes return _ftp_chkerr(f); 14837535Sdes} 14937535Sdes 15037535Sdes/* 15137608Sdes * Transfer file 15237535Sdes */ 15337535Sdesstatic FILE * 15437608Sdes_ftp_transfer(FILE *cf, char *oper, char *file, char *mode, int pasv) 15537535Sdes{ 15637573Sdes struct sockaddr_in sin; 15741869Sdes int e, sd = -1, l; 15837573Sdes char *s; 15937573Sdes FILE *df; 16037571Sdes 16137535Sdes /* change directory */ 16237573Sdes if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 16337573Sdes *s = 0; 16441869Sdes if ((e = _ftp_cmd(cf, "CWD %s" ENDL, file)) != FTP_FILE_ACTION_OK) { 16537573Sdes *s = '/'; 16641869Sdes _ftp_seterr(e); 16737535Sdes return NULL; 16837535Sdes } 16937573Sdes *s++ = '/'; 17037535Sdes } else { 17141869Sdes if ((e = _ftp_cmd(cf, "CWD /" ENDL)) != FTP_FILE_ACTION_OK) { 17241869Sdes _ftp_seterr(e); 17337535Sdes return NULL; 17441869Sdes } 17537535Sdes } 17637535Sdes 17737573Sdes /* s now points to file name */ 17837573Sdes 17937573Sdes /* open data socket */ 18038394Sdes if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { 18140939Sdes _fetch_syserr(); 18237573Sdes return NULL; 18337573Sdes } 18437573Sdes 18537573Sdes if (pasv) { 18637573Sdes u_char addr[6]; 18737573Sdes char *ln, *p; 18837573Sdes int i; 18937573Sdes 19037573Sdes /* send PASV command */ 19141869Sdes if ((e = _ftp_cmd(cf, "PASV" ENDL)) != FTP_PASSIVE_MODE) 19237573Sdes goto ouch; 19337573Sdes 19437573Sdes /* find address and port number. The reply to the PASV command 19537573Sdes is IMHO the one and only weak point in the FTP protocol. */ 19637573Sdes ln = _ftp_last_reply; 19737573Sdes for (p = ln + 3; !isdigit(*p); p++) 19837573Sdes /* nothing */ ; 19937573Sdes for (p--, i = 0; i < 6; i++) { 20037573Sdes p++; /* skip the comma */ 20137573Sdes addr[i] = strtol(p, &p, 10); 20237573Sdes } 20337573Sdes 20437573Sdes /* construct sockaddr for data socket */ 20537573Sdes l = sizeof(sin); 20638394Sdes if (getpeername(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 20737573Sdes goto sysouch; 20837573Sdes bcopy(addr, (char *)&sin.sin_addr, 4); 20937573Sdes bcopy(addr + 4, (char *)&sin.sin_port, 2); 21037573Sdes 21137573Sdes /* connect to data port */ 21238394Sdes if (connect(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) 21337573Sdes goto sysouch; 21437573Sdes 21537573Sdes /* make the server initiate the transfer */ 21641869Sdes e = _ftp_cmd(cf, "%s %s" ENDL, oper, s); 21741869Sdes if (e != FTP_OPEN_DATA_CONNECTION) 21837573Sdes goto ouch; 21937573Sdes 22037573Sdes } else { 22137573Sdes u_int32_t a; 22237573Sdes u_short p; 22337573Sdes int d; 22437573Sdes 22537573Sdes /* find our own address, bind, and listen */ 22637573Sdes l = sizeof(sin); 22738394Sdes if (getsockname(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 22837573Sdes goto sysouch; 22937573Sdes sin.sin_port = 0; 23038394Sdes if (bind(sd, (struct sockaddr *)&sin, l) == -1) 23137573Sdes goto sysouch; 23238394Sdes if (listen(sd, 1) == -1) 23337573Sdes goto sysouch; 23437573Sdes 23537573Sdes /* find what port we're on and tell the server */ 23638394Sdes if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 23737573Sdes goto sysouch; 23837573Sdes a = ntohl(sin.sin_addr.s_addr); 23937573Sdes p = ntohs(sin.sin_port); 24041869Sdes e = _ftp_cmd(cf, "PORT %d,%d,%d,%d,%d,%d" ENDL, 24141869Sdes (a >> 24) & 0xff, (a >> 16) & 0xff, 24241869Sdes (a >> 8) & 0xff, a & 0xff, 24341869Sdes (p >> 8) & 0xff, p & 0xff); 24441869Sdes if (e != FTP_OK) 24537573Sdes goto ouch; 24637573Sdes 24737573Sdes /* make the server initiate the transfer */ 24841869Sdes e = _ftp_cmd(cf, "%s %s" ENDL, oper, s); 24941869Sdes if (e != FTP_OPEN_DATA_CONNECTION) 25037573Sdes goto ouch; 25137573Sdes 25237573Sdes /* accept the incoming connection and go to town */ 25338394Sdes if ((d = accept(sd, NULL, NULL)) == -1) 25437573Sdes goto sysouch; 25537573Sdes close(sd); 25637573Sdes sd = d; 25737573Sdes } 25837573Sdes 25937608Sdes if ((df = fdopen(sd, mode)) == NULL) 26037573Sdes goto sysouch; 26137573Sdes return df; 26237573Sdes 26337573Sdessysouch: 26440939Sdes _fetch_syserr(); 26541869Sdes close(sd); 26641869Sdes return NULL; 26741869Sdes 26837573Sdesouch: 26941869Sdes _ftp_seterr(e); 27037573Sdes close(sd); 27137535Sdes return NULL; 27237535Sdes} 27337535Sdes 27437571Sdes/* 27537571Sdes * Log on to FTP server 27637535Sdes */ 27737571Sdesstatic FILE * 27841862Sdes_ftp_connect(char *host, int port, char *user, char *pwd, int verbose) 27937571Sdes{ 28037608Sdes int sd, e, pp = FTP_DEFAULT_PORT; 28137608Sdes char *p, *q; 28237571Sdes FILE *f; 28337571Sdes 28437608Sdes /* check for proxy */ 28537608Sdes if ((p = getenv("FTP_PROXY")) != NULL) { 28637608Sdes if ((q = strchr(p, ':')) != NULL) { 28737608Sdes /* XXX check that it's a valid number */ 28837608Sdes pp = atoi(q+1); 28937608Sdes } 29037608Sdes if (q) 29137608Sdes *q = 0; 29241923Sdes sd = _fetch_connect(p, pp, verbose); 29337608Sdes if (q) 29437608Sdes *q = ':'; 29537608Sdes } else { 29637608Sdes /* no proxy, go straight to target */ 29741923Sdes sd = _fetch_connect(host, port, verbose); 29837608Sdes } 29937608Sdes 30037608Sdes /* check connection */ 30138394Sdes if (sd == -1) { 30240939Sdes _fetch_syserr(); 30337571Sdes return NULL; 30437571Sdes } 30537608Sdes 30637608Sdes /* streams make life easier */ 30737571Sdes if ((f = fdopen(sd, "r+")) == NULL) { 30840939Sdes _fetch_syserr(); 30941869Sdes close(sd); 31041869Sdes return NULL; 31137571Sdes } 31237571Sdes 31337571Sdes /* expect welcome message */ 31441869Sdes if ((e = _ftp_chkerr(f)) != FTP_SERVICE_READY) 31537571Sdes goto fouch; 31637571Sdes 31737571Sdes /* send user name and password */ 31837608Sdes if (!user || !*user) 31937608Sdes user = FTP_ANONYMOUS_USER; 32037608Sdes e = p ? _ftp_cmd(f, "USER %s@%s@%d" ENDL, user, host, port) 32137608Sdes : _ftp_cmd(f, "USER %s" ENDL, user); 32237608Sdes 32337608Sdes /* did the server request a password? */ 32437608Sdes if (e == FTP_NEED_PASSWORD) { 32537608Sdes if (!pwd || !*pwd) 32637608Sdes pwd = FTP_ANONYMOUS_PASSWORD; 32737573Sdes e = _ftp_cmd(f, "PASS %s" ENDL, pwd); 32837608Sdes } 32937608Sdes 33037608Sdes /* did the server request an account? */ 33141869Sdes if (e == FTP_NEED_ACCOUNT) 33241863Sdes goto fouch; 33337608Sdes 33437608Sdes /* we should be done by now */ 33541869Sdes if (e != FTP_LOGGED_IN) 33637571Sdes goto fouch; 33737571Sdes 33837571Sdes /* might as well select mode and type at once */ 33937571Sdes#ifdef FTP_FORCE_STREAM_MODE 34041869Sdes if ((e = _ftp_cmd(f, "MODE S" ENDL)) != FTP_OK) /* default is S */ 34141869Sdes goto fouch; 34237571Sdes#endif 34341869Sdes if ((e = _ftp_cmd(f, "TYPE I" ENDL)) != FTP_OK) /* default is A */ 34441869Sdes goto fouch; 34537571Sdes 34637571Sdes /* done */ 34737571Sdes return f; 34837571Sdes 34937571Sdesfouch: 35041869Sdes _ftp_seterr(e); 35137571Sdes fclose(f); 35237571Sdes return NULL; 35337571Sdes} 35437571Sdes 35537571Sdes/* 35637571Sdes * Disconnect from server 35737571Sdes */ 35837571Sdesstatic void 35937571Sdes_ftp_disconnect(FILE *f) 36037571Sdes{ 36141863Sdes (void)_ftp_cmd(f, "QUIT" ENDL); 36237571Sdes fclose(f); 36337571Sdes} 36437571Sdes 36537571Sdes/* 36637571Sdes * Check if we're already connected 36737571Sdes */ 36837571Sdesstatic int 36940975Sdes_ftp_isconnected(struct url *url) 37037571Sdes{ 37137571Sdes return (cached_socket 37237571Sdes && (strcmp(url->host, cached_host.host) == 0) 37337571Sdes && (strcmp(url->user, cached_host.user) == 0) 37437571Sdes && (strcmp(url->pwd, cached_host.pwd) == 0) 37537571Sdes && (url->port == cached_host.port)); 37637571Sdes} 37737571Sdes 37837608Sdes/* 37941869Sdes * Check the cache, reconnect if no luck 38037608Sdes */ 38137608Sdesstatic FILE * 38241869Sdes_ftp_cached_connect(struct url *url, char *flags) 38337535Sdes{ 38441869Sdes FILE *cf; 38537535Sdes 38641869Sdes cf = NULL; 38741869Sdes 38837571Sdes /* set default port */ 38937571Sdes if (!url->port) 39037573Sdes url->port = FTP_DEFAULT_PORT; 39137535Sdes 39241863Sdes /* try to use previously cached connection */ 39341863Sdes if (_ftp_isconnected(url)) 39441869Sdes if (_ftp_cmd(cached_socket, "NOOP" ENDL) != -1) 39537571Sdes cf = cached_socket; 39637571Sdes 39737571Sdes /* connect to server */ 39837571Sdes if (!cf) { 39941862Sdes cf = _ftp_connect(url->host, url->port, url->user, url->pwd, 40041862Sdes (strchr(flags, 'v') != NULL)); 40137571Sdes if (!cf) 40237571Sdes return NULL; 40337571Sdes if (cached_socket) 40437571Sdes _ftp_disconnect(cached_socket); 40537571Sdes cached_socket = cf; 40640975Sdes memcpy(&cached_host, url, sizeof(struct url)); 40737535Sdes } 40837571Sdes 40941869Sdes return cf; 41037535Sdes} 41137535Sdes 41237571Sdes/* 41341869Sdes * Get file 41437571Sdes */ 41537535SdesFILE * 41640975SdesfetchGetFTP(struct url *url, char *flags) 41737608Sdes{ 41841869Sdes FILE *cf; 41941869Sdes 42041869Sdes /* connect to server */ 42141869Sdes if ((cf = _ftp_cached_connect(url, flags)) == NULL) 42241869Sdes return NULL; 42341869Sdes 42441869Sdes /* initiate the transfer */ 42541869Sdes return _ftp_transfer(cf, "RETR", url->doc, "r", 42641869Sdes (flags && strchr(flags, 'p'))); 42737608Sdes} 42837608Sdes 42941869Sdes/* 43041869Sdes * Put file 43141869Sdes */ 43237608SdesFILE * 43340975SdesfetchPutFTP(struct url *url, char *flags) 43437535Sdes{ 43541869Sdes FILE *cf; 43641869Sdes 43741869Sdes /* connect to server */ 43841869Sdes if ((cf = _ftp_cached_connect(url, flags)) == NULL) 43941869Sdes return NULL; 44041869Sdes 44141869Sdes /* initiate the transfer */ 44241869Sdes return _ftp_transfer(cf, (flags && strchr(flags, 'a')) ? "APPE" : "STOR", 44341869Sdes url->doc, "w", (flags && strchr(flags, 'p'))); 44437535Sdes} 44540975Sdes 44641869Sdes/* 44741869Sdes * Get file stats 44841869Sdes */ 44940975Sdesint 45040975SdesfetchStatFTP(struct url *url, struct url_stat *us, char *flags) 45140975Sdes{ 45241869Sdes FILE *cf; 45341869Sdes char *ln, *s; 45441869Sdes struct tm tm; 45541869Sdes time_t t; 45641869Sdes int e; 45741869Sdes 45841869Sdes /* connect to server */ 45941869Sdes if ((cf = _ftp_cached_connect(url, flags)) == NULL) 46041869Sdes return -1; 46141869Sdes 46241869Sdes /* change directory */ 46341869Sdes if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) { 46441869Sdes *s = 0; 46541869Sdes if ((e = _ftp_cmd(cf, "CWD %s" ENDL, url->doc)) != FTP_FILE_ACTION_OK) { 46641869Sdes *s = '/'; 46741869Sdes goto ouch; 46841869Sdes } 46941869Sdes *s++ = '/'; 47041869Sdes } else { 47141869Sdes if ((e = _ftp_cmd(cf, "CWD /" ENDL)) != FTP_FILE_ACTION_OK) 47241869Sdes goto ouch; 47341869Sdes } 47441869Sdes 47541869Sdes /* s now points to file name */ 47641869Sdes 47741869Sdes if (_ftp_cmd(cf, "SIZE %s" ENDL, s) != FTP_FILE_STATUS) 47841869Sdes goto ouch; 47941869Sdes for (ln = _ftp_last_reply + 4; *ln && isspace(*ln); ln++) 48041869Sdes /* nothing */ ; 48141869Sdes for (us->size = 0; *ln && isdigit(*ln); ln++) 48241869Sdes us->size = us->size * 10 + *ln - '0'; 48341869Sdes if (*ln && !isspace(*ln)) { 48441869Sdes _ftp_seterr(999); /* XXX should signal a FETCH_PROTO error */ 48541869Sdes return -1; 48641869Sdes } 48741869Sdes 48841869Sdes if ((e = _ftp_cmd(cf, "MDTM %s" ENDL, s)) != FTP_FILE_STATUS) 48941869Sdes goto ouch; 49041869Sdes for (ln = _ftp_last_reply + 4; *ln && isspace(*ln); ln++) 49141869Sdes /* nothing */ ; 49241869Sdes t = time(NULL); 49341869Sdes us->mtime = localtime(&t)->tm_gmtoff; 49441869Sdes sscanf(ln, "%04d%02d%02d%02d%02d%02d", 49541869Sdes &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 49641869Sdes &tm.tm_hour, &tm.tm_min, &tm.tm_sec); 49741869Sdes /* XXX should check the return value from sscanf */ 49841869Sdes tm.tm_mon--; 49941869Sdes tm.tm_year -= 1900; 50041869Sdes tm.tm_isdst = -1; 50141869Sdes tm.tm_gmtoff = 0; 50241869Sdes us->mtime += mktime(&tm); 50341869Sdes us->atime = us->mtime; 50441869Sdes return 0; 50541869Sdes 50641869Sdesouch: 50741869Sdes _ftp_seterr(e); 50840975Sdes return -1; 50940975Sdes} 51041989Sdes 51141989Sdes/* 51241989Sdes * List a directory 51341989Sdes */ 51441989Sdesextern void warnx(char *, ...); 51541989Sdesstruct url_ent * 51641989SdesfetchListFTP(struct url *url, char *flags) 51741989Sdes{ 51841989Sdes warnx("fetchListFTP(): not implemented"); 51941989Sdes return NULL; 52041989Sdes} 521