ftp.c revision 40975
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 * 2840975Sdes * $Id: ftp.c,v 1.6 1998/11/05 19:48:17 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" 7340939Sdes#include "common.h" 7440975Sdes#include "ftperr.inc" 7537535Sdes 7637571Sdes#define FTP_DEFAULT_TO_ANONYMOUS 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 8337573Sdes#define FTP_PASSIVE_MODE 227 8437573Sdes#define FTP_LOGGED_IN 230 8537573Sdes#define FTP_FILE_ACTION_OK 250 8637573Sdes#define FTP_NEED_PASSWORD 331 8737573Sdes#define FTP_NEED_ACCOUNT 332 8837573Sdes 8937571Sdes#define ENDL "\r\n" 9037571Sdes 9140975Sdesstatic struct url cached_host; 9237535Sdesstatic FILE *cached_socket; 9337535Sdes 9437573Sdesstatic char *_ftp_last_reply; 9537571Sdes 9637571Sdes/* 9737535Sdes * Get server response, check that first digit is a '2' 9837535Sdes */ 9937535Sdesstatic int 10037571Sdes_ftp_chkerr(FILE *s, int *e) 10137535Sdes{ 10237535Sdes char *line; 10337535Sdes size_t len; 10440975Sdes int err; 10537535Sdes 10637571Sdes if (e) 10737571Sdes *e = 0; 10837571Sdes 10937535Sdes do { 11037573Sdes if (((line = fgetln(s, &len)) == NULL) || (len < 4)) { 11140939Sdes _fetch_syserr(); 11237535Sdes return -1; 11337571Sdes } 11437535Sdes } while (line[3] == '-'); 11537573Sdes 11637573Sdes _ftp_last_reply = line; 11737535Sdes 11837573Sdes#ifndef NDEBUG 11937573Sdes fprintf(stderr, "\033[1m<<< "); 12037573Sdes fprintf(stderr, "%*.*s", (int)len, (int)len, line); 12137573Sdes fprintf(stderr, "\033[m"); 12237573Sdes#endif 12337573Sdes 12437571Sdes if (!isdigit(line[1]) || !isdigit(line[1]) 12537571Sdes || !isdigit(line[2]) || (line[3] != ' ')) { 12640975Sdes _ftp_seterr(0); 12737535Sdes return -1; 12837571Sdes } 12937535Sdes 13040975Sdes err = (line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0'); 13140975Sdes _ftp_seterr(err); 13237535Sdes 13337535Sdes if (e) 13440975Sdes *e = err; 13537535Sdes 13637535Sdes return (line[0] == '2') - 1; 13737535Sdes} 13837535Sdes 13937535Sdes/* 14037573Sdes * Send a command and check reply 14137535Sdes */ 14237535Sdesstatic int 14337573Sdes_ftp_cmd(FILE *f, char *fmt, ...) 14437535Sdes{ 14537573Sdes va_list ap; 14637573Sdes int e; 14737573Sdes 14837573Sdes va_start(ap, fmt); 14937573Sdes vfprintf(f, fmt, ap); 15037573Sdes#ifndef NDEBUG 15137573Sdes fprintf(stderr, "\033[1m>>> "); 15237573Sdes vfprintf(stderr, fmt, ap); 15337573Sdes fprintf(stderr, "\033[m"); 15437573Sdes#endif 15537573Sdes va_end(ap); 15637571Sdes 15737573Sdes _ftp_chkerr(f, &e); 15837573Sdes return e; 15937535Sdes} 16037535Sdes 16137535Sdes/* 16237608Sdes * Transfer file 16337535Sdes */ 16437535Sdesstatic FILE * 16537608Sdes_ftp_transfer(FILE *cf, char *oper, char *file, char *mode, int pasv) 16637535Sdes{ 16737573Sdes struct sockaddr_in sin; 16837573Sdes int sd = -1, l; 16937573Sdes char *s; 17037573Sdes FILE *df; 17137571Sdes 17237535Sdes /* change directory */ 17337573Sdes if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 17437573Sdes *s = 0; 17537573Sdes if (_ftp_cmd(cf, "CWD %s" ENDL, file) != FTP_FILE_ACTION_OK) { 17637573Sdes *s = '/'; 17737535Sdes return NULL; 17837535Sdes } 17937573Sdes *s++ = '/'; 18037535Sdes } else { 18137573Sdes if (_ftp_cmd(cf, "CWD /" ENDL) != FTP_FILE_ACTION_OK) 18237535Sdes return NULL; 18337535Sdes } 18437535Sdes 18537573Sdes /* s now points to file name */ 18637573Sdes 18737573Sdes /* open data socket */ 18838394Sdes if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { 18940939Sdes _fetch_syserr(); 19037573Sdes return NULL; 19137573Sdes } 19237573Sdes 19337573Sdes if (pasv) { 19437573Sdes u_char addr[6]; 19537573Sdes char *ln, *p; 19637573Sdes int i; 19737573Sdes 19837573Sdes /* send PASV command */ 19937573Sdes if (_ftp_cmd(cf, "PASV" ENDL) != FTP_PASSIVE_MODE) 20037573Sdes goto ouch; 20137573Sdes 20237573Sdes /* find address and port number. The reply to the PASV command 20337573Sdes is IMHO the one and only weak point in the FTP protocol. */ 20437573Sdes ln = _ftp_last_reply; 20537573Sdes for (p = ln + 3; !isdigit(*p); p++) 20637573Sdes /* nothing */ ; 20737573Sdes for (p--, i = 0; i < 6; i++) { 20837573Sdes p++; /* skip the comma */ 20937573Sdes addr[i] = strtol(p, &p, 10); 21037573Sdes } 21137573Sdes 21237573Sdes /* construct sockaddr for data socket */ 21337573Sdes l = sizeof(sin); 21438394Sdes if (getpeername(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 21537573Sdes goto sysouch; 21637573Sdes bcopy(addr, (char *)&sin.sin_addr, 4); 21737573Sdes bcopy(addr + 4, (char *)&sin.sin_port, 2); 21837573Sdes 21937573Sdes /* connect to data port */ 22038394Sdes if (connect(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) 22137573Sdes goto sysouch; 22237573Sdes 22337573Sdes /* make the server initiate the transfer */ 22437608Sdes if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION) 22537573Sdes goto ouch; 22637573Sdes 22737573Sdes } else { 22837573Sdes u_int32_t a; 22937573Sdes u_short p; 23037573Sdes int d; 23137573Sdes 23237573Sdes /* find our own address, bind, and listen */ 23337573Sdes l = sizeof(sin); 23438394Sdes if (getsockname(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 23537573Sdes goto sysouch; 23637573Sdes sin.sin_port = 0; 23738394Sdes if (bind(sd, (struct sockaddr *)&sin, l) == -1) 23837573Sdes goto sysouch; 23938394Sdes if (listen(sd, 1) == -1) 24037573Sdes goto sysouch; 24137573Sdes 24237573Sdes /* find what port we're on and tell the server */ 24338394Sdes if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 24437573Sdes goto sysouch; 24537573Sdes a = ntohl(sin.sin_addr.s_addr); 24637573Sdes p = ntohs(sin.sin_port); 24737573Sdes if (_ftp_cmd(cf, "PORT %d,%d,%d,%d,%d,%d" ENDL, 24837573Sdes (a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff, 24937573Sdes (p >> 8) & 0xff, p & 0xff) != FTP_OK) 25037573Sdes goto ouch; 25137573Sdes 25237573Sdes /* make the server initiate the transfer */ 25337608Sdes if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION) 25437573Sdes goto ouch; 25537573Sdes 25637573Sdes /* accept the incoming connection and go to town */ 25738394Sdes if ((d = accept(sd, NULL, NULL)) == -1) 25837573Sdes goto sysouch; 25937573Sdes close(sd); 26037573Sdes sd = d; 26137573Sdes } 26237573Sdes 26337608Sdes if ((df = fdopen(sd, mode)) == NULL) 26437573Sdes goto sysouch; 26537573Sdes return df; 26637573Sdes 26737573Sdessysouch: 26840939Sdes _fetch_syserr(); 26937573Sdesouch: 27037573Sdes close(sd); 27137535Sdes return NULL; 27237535Sdes} 27337535Sdes 27437571Sdes/* 27537571Sdes * Log on to FTP server 27637535Sdes */ 27737571Sdesstatic FILE * 27837571Sdes_ftp_connect(char *host, int port, char *user, char *pwd) 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; 29237608Sdes sd = fetchConnect(p, pp); 29337608Sdes if (q) 29437608Sdes *q = ':'; 29537608Sdes } else { 29637608Sdes /* no proxy, go straight to target */ 29737608Sdes sd = fetchConnect(host, port); 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(); 30937571Sdes goto ouch; 31037571Sdes } 31137571Sdes 31237571Sdes /* expect welcome message */ 31338394Sdes if (_ftp_chkerr(f, NULL) == -1) 31437571Sdes goto fouch; 31537571Sdes 31637571Sdes /* send user name and password */ 31737608Sdes if (!user || !*user) 31837608Sdes user = FTP_ANONYMOUS_USER; 31937608Sdes e = p ? _ftp_cmd(f, "USER %s@%s@%d" ENDL, user, host, port) 32037608Sdes : _ftp_cmd(f, "USER %s" ENDL, user); 32137608Sdes 32237608Sdes /* did the server request a password? */ 32337608Sdes if (e == FTP_NEED_PASSWORD) { 32437608Sdes if (!pwd || !*pwd) 32537608Sdes pwd = FTP_ANONYMOUS_PASSWORD; 32637573Sdes e = _ftp_cmd(f, "PASS %s" ENDL, pwd); 32737608Sdes } 32837608Sdes 32937608Sdes /* did the server request an account? */ 33037608Sdes if (e == FTP_NEED_ACCOUNT) 33137573Sdes /* help! */ ; 33237608Sdes 33337608Sdes /* we should be done by now */ 33437608Sdes if (e != FTP_LOGGED_IN) 33537571Sdes goto fouch; 33637571Sdes 33737571Sdes /* might as well select mode and type at once */ 33837571Sdes#ifdef FTP_FORCE_STREAM_MODE 33937608Sdes if (_ftp_cmd(f, "MODE S" ENDL) != FTP_OK) /* default is S */ 34037571Sdes goto ouch; 34137571Sdes#endif 34237608Sdes if (_ftp_cmd(f, "TYPE I" ENDL) != FTP_OK) /* default is A */ 34337571Sdes goto ouch; 34437571Sdes 34537571Sdes /* done */ 34637571Sdes return f; 34737571Sdes 34837571Sdesouch: 34937571Sdes close(sd); 35037571Sdes return NULL; 35137571Sdesfouch: 35237571Sdes fclose(f); 35337571Sdes return NULL; 35437571Sdes} 35537571Sdes 35637571Sdes/* 35737571Sdes * Disconnect from server 35837571Sdes */ 35937571Sdesstatic void 36037571Sdes_ftp_disconnect(FILE *f) 36137571Sdes{ 36237573Sdes _ftp_cmd(f, "QUIT" ENDL); 36337571Sdes fclose(f); 36437571Sdes} 36537571Sdes 36637571Sdes/* 36737571Sdes * Check if we're already connected 36837571Sdes */ 36937571Sdesstatic int 37040975Sdes_ftp_isconnected(struct url *url) 37137571Sdes{ 37237571Sdes return (cached_socket 37337571Sdes && (strcmp(url->host, cached_host.host) == 0) 37437571Sdes && (strcmp(url->user, cached_host.user) == 0) 37537571Sdes && (strcmp(url->pwd, cached_host.pwd) == 0) 37637571Sdes && (url->port == cached_host.port)); 37737571Sdes} 37837571Sdes 37937608Sdes/* 38037608Sdes * FTP session 38137608Sdes */ 38237608Sdesstatic FILE * 38340975SdesfetchXxxFTP(struct url *url, char *oper, char *mode, char *flags) 38437535Sdes{ 38537571Sdes FILE *cf = NULL; 38637571Sdes int e; 38737535Sdes 38837571Sdes /* set default port */ 38937571Sdes if (!url->port) 39037573Sdes url->port = FTP_DEFAULT_PORT; 39137535Sdes 39238394Sdes /* try to use previously cached connection; there should be a 226 waiting */ 39337571Sdes if (_ftp_isconnected(url)) { 39437571Sdes _ftp_chkerr(cached_socket, &e); 39537571Sdes if (e > 0) 39637571Sdes cf = cached_socket; 39737535Sdes } 39837571Sdes 39937571Sdes /* connect to server */ 40037571Sdes if (!cf) { 40137571Sdes cf = _ftp_connect(url->host, url->port, url->user, url->pwd); 40237571Sdes if (!cf) 40337571Sdes return NULL; 40437571Sdes if (cached_socket) 40537571Sdes _ftp_disconnect(cached_socket); 40637571Sdes cached_socket = cf; 40740975Sdes memcpy(&cached_host, url, sizeof(struct url)); 40837535Sdes } 40937571Sdes 41037571Sdes /* initiate the transfer */ 41137608Sdes return _ftp_transfer(cf, oper, url->doc, mode, (flags && strchr(flags, 'p'))); 41237535Sdes} 41337535Sdes 41437571Sdes/* 41537608Sdes * Itsy bitsy teeny weenie 41637571Sdes */ 41737535SdesFILE * 41840975SdesfetchGetFTP(struct url *url, char *flags) 41937608Sdes{ 42037608Sdes return fetchXxxFTP(url, "RETR", "r", flags); 42137608Sdes} 42237608Sdes 42337608SdesFILE * 42440975SdesfetchPutFTP(struct url *url, char *flags) 42537535Sdes{ 42637608Sdes if (flags && strchr(flags, 'a')) 42737608Sdes return fetchXxxFTP(url, "APPE", "w", flags); 42837608Sdes else return fetchXxxFTP(url, "STOR", "w", flags); 42937535Sdes} 43040975Sdes 43140975Sdesextern void warnx(char *fmt, ...); 43240975Sdesint 43340975SdesfetchStatFTP(struct url *url, struct url_stat *us, char *flags) 43440975Sdes{ 43540975Sdes warnx("fetchStatFTP(): not implemented"); 43640975Sdes return -1; 43740975Sdes} 43840975Sdes 439