ftp.c revision 41863
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 * 2841863Sdes * $Id: ftp.c,v 1.8 1998/12/16 10:24:55 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> 6737571Sdes#include <unistd.h> 6837535Sdes 6937535Sdes#include "fetch.h" 7040939Sdes#include "common.h" 7141862Sdes#include "ftperr.h" 7237535Sdes 7337535Sdes#define FTP_ANONYMOUS_USER "ftp" 7437535Sdes#define FTP_ANONYMOUS_PASSWORD "ftp" 7537573Sdes#define FTP_DEFAULT_PORT 21 7637535Sdes 7737573Sdes#define FTP_OPEN_DATA_CONNECTION 150 7837573Sdes#define FTP_OK 200 7941863Sdes#define FTP_SERVICE_READY 220 8037573Sdes#define FTP_PASSIVE_MODE 227 8137573Sdes#define FTP_LOGGED_IN 230 8237573Sdes#define FTP_FILE_ACTION_OK 250 8337573Sdes#define FTP_NEED_PASSWORD 331 8437573Sdes#define FTP_NEED_ACCOUNT 332 8537573Sdes 8637571Sdes#define ENDL "\r\n" 8737571Sdes 8840975Sdesstatic struct url cached_host; 8937535Sdesstatic FILE *cached_socket; 9037535Sdes 9137573Sdesstatic char *_ftp_last_reply; 9237571Sdes 9337571Sdes/* 9437535Sdes * Get server response, check that first digit is a '2' 9537535Sdes */ 9637535Sdesstatic int 9741863Sdes_ftp_chkerr(FILE *s) 9837535Sdes{ 9937535Sdes char *line; 10037535Sdes size_t len; 10137535Sdes 10237535Sdes do { 10337573Sdes if (((line = fgetln(s, &len)) == NULL) || (len < 4)) { 10440939Sdes _fetch_syserr(); 10537535Sdes return -1; 10637571Sdes } 10737535Sdes } while (line[3] == '-'); 10837573Sdes 10937573Sdes _ftp_last_reply = line; 11037535Sdes 11137573Sdes#ifndef NDEBUG 11237573Sdes fprintf(stderr, "\033[1m<<< "); 11337573Sdes fprintf(stderr, "%*.*s", (int)len, (int)len, line); 11437573Sdes fprintf(stderr, "\033[m"); 11537573Sdes#endif 11637573Sdes 11737571Sdes if (!isdigit(line[1]) || !isdigit(line[1]) 11837571Sdes || !isdigit(line[2]) || (line[3] != ' ')) { 11937535Sdes return -1; 12037571Sdes } 12137535Sdes 12241863Sdes return (line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0'); 12337535Sdes} 12437535Sdes 12537535Sdes/* 12637573Sdes * Send a command and check reply 12737535Sdes */ 12837535Sdesstatic int 12937573Sdes_ftp_cmd(FILE *f, char *fmt, ...) 13037535Sdes{ 13137573Sdes va_list ap; 13237573Sdes 13337573Sdes va_start(ap, fmt); 13437573Sdes vfprintf(f, fmt, ap); 13537573Sdes#ifndef NDEBUG 13637573Sdes fprintf(stderr, "\033[1m>>> "); 13737573Sdes vfprintf(stderr, fmt, ap); 13837573Sdes fprintf(stderr, "\033[m"); 13937573Sdes#endif 14037573Sdes va_end(ap); 14137571Sdes 14241863Sdes return _ftp_chkerr(f); 14337535Sdes} 14437535Sdes 14537535Sdes/* 14637608Sdes * Transfer file 14737535Sdes */ 14837535Sdesstatic FILE * 14937608Sdes_ftp_transfer(FILE *cf, char *oper, char *file, char *mode, int pasv) 15037535Sdes{ 15137573Sdes struct sockaddr_in sin; 15237573Sdes int sd = -1, l; 15337573Sdes char *s; 15437573Sdes FILE *df; 15537571Sdes 15637535Sdes /* change directory */ 15737573Sdes if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 15837573Sdes *s = 0; 15937573Sdes if (_ftp_cmd(cf, "CWD %s" ENDL, file) != FTP_FILE_ACTION_OK) { 16037573Sdes *s = '/'; 16137535Sdes return NULL; 16237535Sdes } 16337573Sdes *s++ = '/'; 16437535Sdes } else { 16537573Sdes if (_ftp_cmd(cf, "CWD /" ENDL) != FTP_FILE_ACTION_OK) 16637535Sdes return NULL; 16737535Sdes } 16837535Sdes 16937573Sdes /* s now points to file name */ 17037573Sdes 17137573Sdes /* open data socket */ 17238394Sdes if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { 17340939Sdes _fetch_syserr(); 17437573Sdes return NULL; 17537573Sdes } 17637573Sdes 17737573Sdes if (pasv) { 17837573Sdes u_char addr[6]; 17937573Sdes char *ln, *p; 18037573Sdes int i; 18137573Sdes 18237573Sdes /* send PASV command */ 18337573Sdes if (_ftp_cmd(cf, "PASV" ENDL) != FTP_PASSIVE_MODE) 18437573Sdes goto ouch; 18537573Sdes 18637573Sdes /* find address and port number. The reply to the PASV command 18737573Sdes is IMHO the one and only weak point in the FTP protocol. */ 18837573Sdes ln = _ftp_last_reply; 18937573Sdes for (p = ln + 3; !isdigit(*p); p++) 19037573Sdes /* nothing */ ; 19137573Sdes for (p--, i = 0; i < 6; i++) { 19237573Sdes p++; /* skip the comma */ 19337573Sdes addr[i] = strtol(p, &p, 10); 19437573Sdes } 19537573Sdes 19637573Sdes /* construct sockaddr for data socket */ 19737573Sdes l = sizeof(sin); 19838394Sdes if (getpeername(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 19937573Sdes goto sysouch; 20037573Sdes bcopy(addr, (char *)&sin.sin_addr, 4); 20137573Sdes bcopy(addr + 4, (char *)&sin.sin_port, 2); 20237573Sdes 20337573Sdes /* connect to data port */ 20438394Sdes if (connect(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) 20537573Sdes goto sysouch; 20637573Sdes 20737573Sdes /* make the server initiate the transfer */ 20837608Sdes if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION) 20937573Sdes goto ouch; 21037573Sdes 21137573Sdes } else { 21237573Sdes u_int32_t a; 21337573Sdes u_short p; 21437573Sdes int d; 21537573Sdes 21637573Sdes /* find our own address, bind, and listen */ 21737573Sdes l = sizeof(sin); 21838394Sdes if (getsockname(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 21937573Sdes goto sysouch; 22037573Sdes sin.sin_port = 0; 22138394Sdes if (bind(sd, (struct sockaddr *)&sin, l) == -1) 22237573Sdes goto sysouch; 22338394Sdes if (listen(sd, 1) == -1) 22437573Sdes goto sysouch; 22537573Sdes 22637573Sdes /* find what port we're on and tell the server */ 22738394Sdes if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 22837573Sdes goto sysouch; 22937573Sdes a = ntohl(sin.sin_addr.s_addr); 23037573Sdes p = ntohs(sin.sin_port); 23137573Sdes if (_ftp_cmd(cf, "PORT %d,%d,%d,%d,%d,%d" ENDL, 23237573Sdes (a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff, 23337573Sdes (p >> 8) & 0xff, p & 0xff) != FTP_OK) 23437573Sdes goto ouch; 23537573Sdes 23637573Sdes /* make the server initiate the transfer */ 23737608Sdes if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION) 23837573Sdes goto ouch; 23937573Sdes 24037573Sdes /* accept the incoming connection and go to town */ 24138394Sdes if ((d = accept(sd, NULL, NULL)) == -1) 24237573Sdes goto sysouch; 24337573Sdes close(sd); 24437573Sdes sd = d; 24537573Sdes } 24637573Sdes 24737608Sdes if ((df = fdopen(sd, mode)) == NULL) 24837573Sdes goto sysouch; 24937573Sdes return df; 25037573Sdes 25137573Sdessysouch: 25240939Sdes _fetch_syserr(); 25337573Sdesouch: 25437573Sdes close(sd); 25537535Sdes return NULL; 25637535Sdes} 25737535Sdes 25837571Sdes/* 25937571Sdes * Log on to FTP server 26037535Sdes */ 26137571Sdesstatic FILE * 26241862Sdes_ftp_connect(char *host, int port, char *user, char *pwd, int verbose) 26337571Sdes{ 26437608Sdes int sd, e, pp = FTP_DEFAULT_PORT; 26537608Sdes char *p, *q; 26637571Sdes FILE *f; 26737571Sdes 26837608Sdes /* check for proxy */ 26937608Sdes if ((p = getenv("FTP_PROXY")) != NULL) { 27037608Sdes if ((q = strchr(p, ':')) != NULL) { 27137608Sdes /* XXX check that it's a valid number */ 27237608Sdes pp = atoi(q+1); 27337608Sdes } 27437608Sdes if (q) 27537608Sdes *q = 0; 27641862Sdes sd = fetchConnect(p, pp, verbose); 27737608Sdes if (q) 27837608Sdes *q = ':'; 27937608Sdes } else { 28037608Sdes /* no proxy, go straight to target */ 28141862Sdes sd = fetchConnect(host, port, verbose); 28237608Sdes } 28337608Sdes 28437608Sdes /* check connection */ 28538394Sdes if (sd == -1) { 28640939Sdes _fetch_syserr(); 28737571Sdes return NULL; 28837571Sdes } 28937608Sdes 29037608Sdes /* streams make life easier */ 29137571Sdes if ((f = fdopen(sd, "r+")) == NULL) { 29240939Sdes _fetch_syserr(); 29337571Sdes goto ouch; 29437571Sdes } 29537571Sdes 29637571Sdes /* expect welcome message */ 29741863Sdes if ((e = _ftp_chkerr(f)) != FTP_SERVICE_READY) { 29841863Sdes _ftp_seterr(e); 29937571Sdes goto fouch; 30041863Sdes } 30137571Sdes 30237571Sdes /* send user name and password */ 30337608Sdes if (!user || !*user) 30437608Sdes user = FTP_ANONYMOUS_USER; 30537608Sdes e = p ? _ftp_cmd(f, "USER %s@%s@%d" ENDL, user, host, port) 30637608Sdes : _ftp_cmd(f, "USER %s" ENDL, user); 30737608Sdes 30837608Sdes /* did the server request a password? */ 30937608Sdes if (e == FTP_NEED_PASSWORD) { 31037608Sdes if (!pwd || !*pwd) 31137608Sdes pwd = FTP_ANONYMOUS_PASSWORD; 31237573Sdes e = _ftp_cmd(f, "PASS %s" ENDL, pwd); 31337608Sdes } 31437608Sdes 31537608Sdes /* did the server request an account? */ 31641863Sdes if (e == FTP_NEED_ACCOUNT) { 31741863Sdes _ftp_seterr(e); 31841863Sdes goto fouch; 31941863Sdes } 32037608Sdes 32137608Sdes /* we should be done by now */ 32241863Sdes if (e != FTP_LOGGED_IN) { 32341863Sdes _ftp_seterr(e); 32437571Sdes goto fouch; 32541863Sdes } 32637571Sdes 32737571Sdes /* might as well select mode and type at once */ 32837571Sdes#ifdef FTP_FORCE_STREAM_MODE 32937608Sdes if (_ftp_cmd(f, "MODE S" ENDL) != FTP_OK) /* default is S */ 33037571Sdes goto ouch; 33137571Sdes#endif 33237608Sdes if (_ftp_cmd(f, "TYPE I" ENDL) != FTP_OK) /* default is A */ 33337571Sdes goto ouch; 33437571Sdes 33537571Sdes /* done */ 33637571Sdes return f; 33737571Sdes 33837571Sdesouch: 33937571Sdes close(sd); 34037571Sdes return NULL; 34137571Sdesfouch: 34237571Sdes fclose(f); 34337571Sdes return NULL; 34437571Sdes} 34537571Sdes 34637571Sdes/* 34737571Sdes * Disconnect from server 34837571Sdes */ 34937571Sdesstatic void 35037571Sdes_ftp_disconnect(FILE *f) 35137571Sdes{ 35241863Sdes (void)_ftp_cmd(f, "QUIT" ENDL); 35337571Sdes fclose(f); 35437571Sdes} 35537571Sdes 35637571Sdes/* 35737571Sdes * Check if we're already connected 35837571Sdes */ 35937571Sdesstatic int 36040975Sdes_ftp_isconnected(struct url *url) 36137571Sdes{ 36237571Sdes return (cached_socket 36337571Sdes && (strcmp(url->host, cached_host.host) == 0) 36437571Sdes && (strcmp(url->user, cached_host.user) == 0) 36537571Sdes && (strcmp(url->pwd, cached_host.pwd) == 0) 36637571Sdes && (url->port == cached_host.port)); 36737571Sdes} 36837571Sdes 36937608Sdes/* 37037608Sdes * FTP session 37137608Sdes */ 37237608Sdesstatic FILE * 37340975SdesfetchXxxFTP(struct url *url, char *oper, char *mode, char *flags) 37437535Sdes{ 37537571Sdes FILE *cf = NULL; 37637535Sdes 37737571Sdes /* set default port */ 37837571Sdes if (!url->port) 37937573Sdes url->port = FTP_DEFAULT_PORT; 38037535Sdes 38141863Sdes /* try to use previously cached connection */ 38241863Sdes if (_ftp_isconnected(url)) 38341863Sdes if (_ftp_cmd(cached_socket, "NOOP" ENDL) > 0) 38437571Sdes cf = cached_socket; 38537571Sdes 38637571Sdes /* connect to server */ 38737571Sdes if (!cf) { 38841862Sdes cf = _ftp_connect(url->host, url->port, url->user, url->pwd, 38941862Sdes (strchr(flags, 'v') != NULL)); 39037571Sdes if (!cf) 39137571Sdes return NULL; 39237571Sdes if (cached_socket) 39337571Sdes _ftp_disconnect(cached_socket); 39437571Sdes cached_socket = cf; 39540975Sdes memcpy(&cached_host, url, sizeof(struct url)); 39637535Sdes } 39737571Sdes 39837571Sdes /* initiate the transfer */ 39941863Sdes return _ftp_transfer(cf, oper, url->doc, mode, 40041863Sdes (flags && strchr(flags, 'p'))); 40137535Sdes} 40237535Sdes 40337571Sdes/* 40437608Sdes * Itsy bitsy teeny weenie 40537571Sdes */ 40637535SdesFILE * 40740975SdesfetchGetFTP(struct url *url, char *flags) 40837608Sdes{ 40937608Sdes return fetchXxxFTP(url, "RETR", "r", flags); 41037608Sdes} 41137608Sdes 41237608SdesFILE * 41340975SdesfetchPutFTP(struct url *url, char *flags) 41437535Sdes{ 41537608Sdes if (flags && strchr(flags, 'a')) 41637608Sdes return fetchXxxFTP(url, "APPE", "w", flags); 41737608Sdes else return fetchXxxFTP(url, "STOR", "w", flags); 41837535Sdes} 41940975Sdes 42040975Sdesextern void warnx(char *fmt, ...); 42140975Sdesint 42240975SdesfetchStatFTP(struct url *url, struct url_stat *us, char *flags) 42340975Sdes{ 42440975Sdes warnx("fetchStatFTP(): not implemented"); 42540975Sdes return -1; 42640975Sdes} 427