ftp.c revision 38394
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 * 2838394Sdes * $Id: ftp.c,v 1.4 1998/07/12 22:34:39 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 5837535Sdes#include <sys/types.h> 5937535Sdes#include <sys/socket.h> 6037535Sdes#include <netinet/in.h> 6137571Sdes#include <sys/errno.h> 6237535Sdes 6337535Sdes#include <ctype.h> 6437571Sdes#include <errno.h> 6537535Sdes#include <netdb.h> 6637573Sdes#include <stdarg.h> 6737535Sdes#include <stdio.h> 6837571Sdes#include <stdlib.h> 6937535Sdes#include <string.h> 7037571Sdes#include <unistd.h> 7137535Sdes 7237535Sdes#include "fetch.h" 7337535Sdes#include "ftperr.c" 7437535Sdes 7537571Sdes#define FTP_DEFAULT_TO_ANONYMOUS 7637535Sdes#define FTP_ANONYMOUS_USER "ftp" 7737535Sdes#define FTP_ANONYMOUS_PASSWORD "ftp" 7837573Sdes#define FTP_DEFAULT_PORT 21 7937535Sdes 8037573Sdes#define FTP_OPEN_DATA_CONNECTION 150 8137573Sdes#define FTP_OK 200 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 9037535Sdesstatic url_t cached_host; 9137535Sdesstatic FILE *cached_socket; 9237535Sdes 9337573Sdesstatic char *_ftp_last_reply; 9437571Sdes 9537571Sdes/* 9637571Sdes * Map error code to string 9737571Sdes */ 9837571Sdesstatic const char * 9937571Sdes_ftp_errstring(int e) 10037535Sdes{ 10137571Sdes struct ftperr *p = _ftp_errlist; 10237571Sdes 10337571Sdes while ((p->num != -1) && (p->num != e)) 10437571Sdes p++; 10537571Sdes 10637571Sdes return p->string; 10737535Sdes} 10837535Sdes 10937535Sdes/* 11037571Sdes * Set error code 11137571Sdes */ 11237571Sdesstatic void 11337571Sdes_ftp_seterr(int e) 11437571Sdes{ 11537571Sdes fetchLastErrCode = e; 11637571Sdes fetchLastErrText = _ftp_errstring(e); 11737571Sdes} 11837571Sdes 11937571Sdes/* 12037571Sdes * Set error code according to errno 12137571Sdes */ 12237571Sdesstatic void 12337571Sdes_ftp_syserr(void) 12437571Sdes{ 12537571Sdes fetchLastErrCode = errno; 12637571Sdes fetchLastErrText = strerror(errno); 12737571Sdes} 12837571Sdes 12937571Sdes/* 13037535Sdes * Get server response, check that first digit is a '2' 13137535Sdes */ 13237535Sdesstatic int 13337571Sdes_ftp_chkerr(FILE *s, int *e) 13437535Sdes{ 13537535Sdes char *line; 13637535Sdes size_t len; 13737535Sdes 13837571Sdes if (e) 13937571Sdes *e = 0; 14037571Sdes 14137535Sdes do { 14237573Sdes if (((line = fgetln(s, &len)) == NULL) || (len < 4)) { 14337571Sdes _ftp_syserr(); 14437535Sdes return -1; 14537571Sdes } 14637535Sdes } while (line[3] == '-'); 14737573Sdes 14837573Sdes _ftp_last_reply = line; 14937535Sdes 15037573Sdes#ifndef NDEBUG 15137573Sdes fprintf(stderr, "\033[1m<<< "); 15237573Sdes fprintf(stderr, "%*.*s", (int)len, (int)len, line); 15337573Sdes fprintf(stderr, "\033[m"); 15437573Sdes#endif 15537573Sdes 15637571Sdes if (!isdigit(line[1]) || !isdigit(line[1]) 15737571Sdes || !isdigit(line[2]) || (line[3] != ' ')) { 15837571Sdes _ftp_seterr(-1); 15937535Sdes return -1; 16037571Sdes } 16137535Sdes 16237571Sdes _ftp_seterr((line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0')); 16337535Sdes 16437535Sdes if (e) 16537571Sdes *e = fetchLastErrCode; 16637535Sdes 16737535Sdes return (line[0] == '2') - 1; 16837535Sdes} 16937535Sdes 17037535Sdes/* 17137573Sdes * Send a command and check reply 17237535Sdes */ 17337535Sdesstatic int 17437573Sdes_ftp_cmd(FILE *f, char *fmt, ...) 17537535Sdes{ 17637573Sdes va_list ap; 17737573Sdes int e; 17837573Sdes 17937573Sdes va_start(ap, fmt); 18037573Sdes vfprintf(f, fmt, ap); 18137573Sdes#ifndef NDEBUG 18237573Sdes fprintf(stderr, "\033[1m>>> "); 18337573Sdes vfprintf(stderr, fmt, ap); 18437573Sdes fprintf(stderr, "\033[m"); 18537573Sdes#endif 18637573Sdes va_end(ap); 18737571Sdes 18837573Sdes _ftp_chkerr(f, &e); 18937573Sdes return e; 19037535Sdes} 19137535Sdes 19237535Sdes/* 19337608Sdes * Transfer file 19437535Sdes */ 19537535Sdesstatic FILE * 19637608Sdes_ftp_transfer(FILE *cf, char *oper, char *file, char *mode, int pasv) 19737535Sdes{ 19837573Sdes struct sockaddr_in sin; 19937573Sdes int sd = -1, l; 20037573Sdes char *s; 20137573Sdes FILE *df; 20237571Sdes 20337535Sdes /* change directory */ 20437573Sdes if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 20537573Sdes *s = 0; 20637573Sdes if (_ftp_cmd(cf, "CWD %s" ENDL, file) != FTP_FILE_ACTION_OK) { 20737573Sdes *s = '/'; 20837535Sdes return NULL; 20937535Sdes } 21037573Sdes *s++ = '/'; 21137535Sdes } else { 21237573Sdes if (_ftp_cmd(cf, "CWD /" ENDL) != FTP_FILE_ACTION_OK) 21337535Sdes return NULL; 21437535Sdes } 21537535Sdes 21637573Sdes /* s now points to file name */ 21737573Sdes 21837573Sdes /* open data socket */ 21938394Sdes if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { 22037573Sdes _ftp_syserr(); 22137573Sdes return NULL; 22237573Sdes } 22337573Sdes 22437573Sdes if (pasv) { 22537573Sdes u_char addr[6]; 22637573Sdes char *ln, *p; 22737573Sdes int i; 22837573Sdes 22937573Sdes /* send PASV command */ 23037573Sdes if (_ftp_cmd(cf, "PASV" ENDL) != FTP_PASSIVE_MODE) 23137573Sdes goto ouch; 23237573Sdes 23337573Sdes /* find address and port number. The reply to the PASV command 23437573Sdes is IMHO the one and only weak point in the FTP protocol. */ 23537573Sdes ln = _ftp_last_reply; 23637573Sdes for (p = ln + 3; !isdigit(*p); p++) 23737573Sdes /* nothing */ ; 23837573Sdes for (p--, i = 0; i < 6; i++) { 23937573Sdes p++; /* skip the comma */ 24037573Sdes addr[i] = strtol(p, &p, 10); 24137573Sdes } 24237573Sdes 24337573Sdes /* construct sockaddr for data socket */ 24437573Sdes l = sizeof(sin); 24538394Sdes if (getpeername(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 24637573Sdes goto sysouch; 24737573Sdes bcopy(addr, (char *)&sin.sin_addr, 4); 24837573Sdes bcopy(addr + 4, (char *)&sin.sin_port, 2); 24937573Sdes 25037573Sdes /* connect to data port */ 25138394Sdes if (connect(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) 25237573Sdes goto sysouch; 25337573Sdes 25437573Sdes /* make the server initiate the transfer */ 25537608Sdes if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION) 25637573Sdes goto ouch; 25737573Sdes 25837573Sdes } else { 25937573Sdes u_int32_t a; 26037573Sdes u_short p; 26137573Sdes int d; 26237573Sdes 26337573Sdes /* find our own address, bind, and listen */ 26437573Sdes l = sizeof(sin); 26538394Sdes if (getsockname(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 26637573Sdes goto sysouch; 26737573Sdes sin.sin_port = 0; 26838394Sdes if (bind(sd, (struct sockaddr *)&sin, l) == -1) 26937573Sdes goto sysouch; 27038394Sdes if (listen(sd, 1) == -1) 27137573Sdes goto sysouch; 27237573Sdes 27337573Sdes /* find what port we're on and tell the server */ 27438394Sdes if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 27537573Sdes goto sysouch; 27637573Sdes a = ntohl(sin.sin_addr.s_addr); 27737573Sdes p = ntohs(sin.sin_port); 27837573Sdes if (_ftp_cmd(cf, "PORT %d,%d,%d,%d,%d,%d" ENDL, 27937573Sdes (a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff, 28037573Sdes (p >> 8) & 0xff, p & 0xff) != FTP_OK) 28137573Sdes goto ouch; 28237573Sdes 28337573Sdes /* make the server initiate the transfer */ 28437608Sdes if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION) 28537573Sdes goto ouch; 28637573Sdes 28737573Sdes /* accept the incoming connection and go to town */ 28838394Sdes if ((d = accept(sd, NULL, NULL)) == -1) 28937573Sdes goto sysouch; 29037573Sdes close(sd); 29137573Sdes sd = d; 29237573Sdes } 29337573Sdes 29437608Sdes if ((df = fdopen(sd, mode)) == NULL) 29537573Sdes goto sysouch; 29637573Sdes return df; 29737573Sdes 29837573Sdessysouch: 29937573Sdes _ftp_syserr(); 30037573Sdesouch: 30137573Sdes close(sd); 30237535Sdes return NULL; 30337535Sdes} 30437535Sdes 30537571Sdes/* 30637571Sdes * Log on to FTP server 30737535Sdes */ 30837571Sdesstatic FILE * 30937571Sdes_ftp_connect(char *host, int port, char *user, char *pwd) 31037571Sdes{ 31137608Sdes int sd, e, pp = FTP_DEFAULT_PORT; 31237608Sdes char *p, *q; 31337571Sdes FILE *f; 31437571Sdes 31537608Sdes /* check for proxy */ 31637608Sdes if ((p = getenv("FTP_PROXY")) != NULL) { 31737608Sdes if ((q = strchr(p, ':')) != NULL) { 31837608Sdes /* XXX check that it's a valid number */ 31937608Sdes pp = atoi(q+1); 32037608Sdes } 32137608Sdes if (q) 32237608Sdes *q = 0; 32337608Sdes sd = fetchConnect(p, pp); 32437608Sdes if (q) 32537608Sdes *q = ':'; 32637608Sdes } else { 32737608Sdes /* no proxy, go straight to target */ 32837608Sdes sd = fetchConnect(host, port); 32937608Sdes } 33037608Sdes 33137608Sdes /* check connection */ 33238394Sdes if (sd == -1) { 33337571Sdes _ftp_syserr(); 33437571Sdes return NULL; 33537571Sdes } 33637608Sdes 33737608Sdes /* streams make life easier */ 33837571Sdes if ((f = fdopen(sd, "r+")) == NULL) { 33937571Sdes _ftp_syserr(); 34037571Sdes goto ouch; 34137571Sdes } 34237571Sdes 34337571Sdes /* expect welcome message */ 34438394Sdes if (_ftp_chkerr(f, NULL) == -1) 34537571Sdes goto fouch; 34637571Sdes 34737571Sdes /* send user name and password */ 34837608Sdes if (!user || !*user) 34937608Sdes user = FTP_ANONYMOUS_USER; 35037608Sdes e = p ? _ftp_cmd(f, "USER %s@%s@%d" ENDL, user, host, port) 35137608Sdes : _ftp_cmd(f, "USER %s" ENDL, user); 35237608Sdes 35337608Sdes /* did the server request a password? */ 35437608Sdes if (e == FTP_NEED_PASSWORD) { 35537608Sdes if (!pwd || !*pwd) 35637608Sdes pwd = FTP_ANONYMOUS_PASSWORD; 35737573Sdes e = _ftp_cmd(f, "PASS %s" ENDL, pwd); 35837608Sdes } 35937608Sdes 36037608Sdes /* did the server request an account? */ 36137608Sdes if (e == FTP_NEED_ACCOUNT) 36237573Sdes /* help! */ ; 36337608Sdes 36437608Sdes /* we should be done by now */ 36537608Sdes if (e != FTP_LOGGED_IN) 36637571Sdes goto fouch; 36737571Sdes 36837571Sdes /* might as well select mode and type at once */ 36937571Sdes#ifdef FTP_FORCE_STREAM_MODE 37037608Sdes if (_ftp_cmd(f, "MODE S" ENDL) != FTP_OK) /* default is S */ 37137571Sdes goto ouch; 37237571Sdes#endif 37337608Sdes if (_ftp_cmd(f, "TYPE I" ENDL) != FTP_OK) /* default is A */ 37437571Sdes goto ouch; 37537571Sdes 37637571Sdes /* done */ 37737571Sdes return f; 37837571Sdes 37937571Sdesouch: 38037571Sdes close(sd); 38137571Sdes return NULL; 38237571Sdesfouch: 38337571Sdes fclose(f); 38437571Sdes return NULL; 38537571Sdes} 38637571Sdes 38737571Sdes/* 38837571Sdes * Disconnect from server 38937571Sdes */ 39037571Sdesstatic void 39137571Sdes_ftp_disconnect(FILE *f) 39237571Sdes{ 39337573Sdes _ftp_cmd(f, "QUIT" ENDL); 39437571Sdes fclose(f); 39537571Sdes} 39637571Sdes 39737571Sdes/* 39837571Sdes * Check if we're already connected 39937571Sdes */ 40037571Sdesstatic int 40137571Sdes_ftp_isconnected(url_t *url) 40237571Sdes{ 40337571Sdes return (cached_socket 40437571Sdes && (strcmp(url->host, cached_host.host) == 0) 40537571Sdes && (strcmp(url->user, cached_host.user) == 0) 40637571Sdes && (strcmp(url->pwd, cached_host.pwd) == 0) 40737571Sdes && (url->port == cached_host.port)); 40837571Sdes} 40937571Sdes 41037608Sdes/* 41137608Sdes * FTP session 41237608Sdes */ 41337608Sdesstatic FILE * 41437608SdesfetchXxxFTP(url_t *url, char *oper, char *mode, char *flags) 41537535Sdes{ 41637571Sdes FILE *cf = NULL; 41737571Sdes int e; 41837535Sdes 41937571Sdes /* set default port */ 42037571Sdes if (!url->port) 42137573Sdes url->port = FTP_DEFAULT_PORT; 42237535Sdes 42338394Sdes /* try to use previously cached connection; there should be a 226 waiting */ 42437571Sdes if (_ftp_isconnected(url)) { 42537571Sdes _ftp_chkerr(cached_socket, &e); 42637571Sdes if (e > 0) 42737571Sdes cf = cached_socket; 42837535Sdes } 42937571Sdes 43037571Sdes /* connect to server */ 43137571Sdes if (!cf) { 43237571Sdes cf = _ftp_connect(url->host, url->port, url->user, url->pwd); 43337571Sdes if (!cf) 43437571Sdes return NULL; 43537571Sdes if (cached_socket) 43637571Sdes _ftp_disconnect(cached_socket); 43737571Sdes cached_socket = cf; 43837571Sdes memcpy(&cached_host, url, sizeof(url_t)); 43937535Sdes } 44037571Sdes 44137571Sdes /* initiate the transfer */ 44237608Sdes return _ftp_transfer(cf, oper, url->doc, mode, (flags && strchr(flags, 'p'))); 44337535Sdes} 44437535Sdes 44537571Sdes/* 44637608Sdes * Itsy bitsy teeny weenie 44737571Sdes */ 44837535SdesFILE * 44937608SdesfetchGetFTP(url_t *url, char *flags) 45037608Sdes{ 45137608Sdes return fetchXxxFTP(url, "RETR", "r", flags); 45237608Sdes} 45337608Sdes 45437608SdesFILE * 45537535SdesfetchPutFTP(url_t *url, char *flags) 45637535Sdes{ 45737608Sdes if (flags && strchr(flags, 'a')) 45837608Sdes return fetchXxxFTP(url, "APPE", "w", flags); 45937608Sdes else return fetchXxxFTP(url, "STOR", "w", flags); 46037535Sdes} 461