ftp.c revision 41862
1226890Sdim/*- 2193326Sed * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav 3193326Sed * All rights reserved. 4193326Sed * 5193326Sed * Redistribution and use in source and binary forms, with or without 6193326Sed * modification, are permitted provided that the following conditions 7193326Sed * are met: 8193326Sed * 1. Redistributions of source code must retain the above copyright 9193326Sed * notice, this list of conditions and the following disclaimer 10193326Sed * in this position and unchanged. 11226890Sdim * 2. Redistributions in binary form must reproduce the above copyright 12193326Sed * notice, this list of conditions and the following disclaimer in the 13193326Sed * documentation and/or other materials provided with the distribution. 14193326Sed * 3. The name of the author may not be used to endorse or promote products 15212904Sdim * derived from this software without specific prior written permission 16212904Sdim * 17193326Sed * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18223017Sdim * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19252723Sdim * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20193326Sed * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21212904Sdim * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22193326Sed * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23193326Sed * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24193326Sed * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25193326Sed * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26193326Sed * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27193326Sed * 28193326Sed * $Id: ftp.c,v 1.7 1998/11/06 22:14:08 des Exp $ 29193326Sed */ 30193326Sed 31193326Sed/* 32193326Sed * Portions of this code were taken from or based on ftpio.c: 33193326Sed * 34198092Srdivacky * ---------------------------------------------------------------------------- 35193326Sed * "THE BEER-WARE LICENSE" (Revision 42): 36193326Sed * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you 37193326Sed * can do whatever you want with this stuff. If we meet some day, and you think 38193326Sed * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 39193326Sed * ---------------------------------------------------------------------------- 40193326Sed * 41193326Sed * Major Changelog: 42193326Sed * 43193326Sed * Dag-Erling Co�dan Sm�rgrav 44198092Srdivacky * 9 Jun 1998 45235633Sdim * 46208600Srdivacky * Incorporated into libfetch 47198092Srdivacky * 48235633Sdim * Jordan K. Hubbard 49208600Srdivacky * 17 Jan 1996 50208600Srdivacky * 51208600Srdivacky * Turned inside out. Now returns xfers as new file ids, not as a special 52208600Srdivacky * `state' of FTP_t 53193326Sed * 54193326Sed * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $ 55198092Srdivacky * 56208600Srdivacky */ 57208600Srdivacky 58208600Srdivacky#include <sys/param.h> 59193326Sed#include <sys/socket.h> 60198092Srdivacky#include <netinet/in.h> 61226890Sdim#include <sys/errno.h> 62193326Sed 63226890Sdim#include <ctype.h> 64208600Srdivacky#include <errno.h> 65226890Sdim#include <netdb.h> 66226890Sdim#include <stdarg.h> 67193326Sed#include <stdio.h> 68193326Sed#include <stdlib.h> 69193326Sed#include <string.h> 70210299Sed#include <unistd.h> 71224145Sdim 72224145Sdim#include "fetch.h" 73224145Sdim#include "common.h" 74224145Sdim#include "ftperr.h" 75193326Sed 76208600Srdivacky#define FTP_DEFAULT_TO_ANONYMOUS 77235633Sdim#define FTP_ANONYMOUS_USER "ftp" 78208600Srdivacky#define FTP_ANONYMOUS_PASSWORD "ftp" 79218893Sdim#define FTP_DEFAULT_PORT 21 80226890Sdim 81235633Sdim#define FTP_OPEN_DATA_CONNECTION 150 82235633Sdim#define FTP_OK 200 83208600Srdivacky#define FTP_PASSIVE_MODE 227 84208600Srdivacky#define FTP_LOGGED_IN 230 85193326Sed#define FTP_FILE_ACTION_OK 250 86193326Sed#define FTP_NEED_PASSWORD 331 87193326Sed#define FTP_NEED_ACCOUNT 332 88193326Sed 89193326Sed#define ENDL "\r\n" 90193326Sed 91208600Srdivackystatic struct url cached_host; 92198092Srdivackystatic FILE *cached_socket; 93193326Sed 94193326Sedstatic char *_ftp_last_reply; 95224145Sdim 96224145Sdim/* 97198092Srdivacky * Get server response, check that first digit is a '2' 98193326Sed */ 99193326Sedstatic int 100208600Srdivacky_ftp_chkerr(FILE *s, int *e) 101193326Sed{ 102198092Srdivacky char *line; 103208600Srdivacky size_t len; 104208600Srdivacky int err; 105208600Srdivacky 106208600Srdivacky if (e) 107208600Srdivacky *e = 0; 108208600Srdivacky 109208600Srdivacky do { 110208600Srdivacky if (((line = fgetln(s, &len)) == NULL) || (len < 4)) { 111208600Srdivacky _fetch_syserr(); 112208600Srdivacky return -1; 113208600Srdivacky } 114208600Srdivacky } while (line[3] == '-'); 115208600Srdivacky 116208600Srdivacky _ftp_last_reply = line; 117208600Srdivacky 118208600Srdivacky#ifndef NDEBUG 119208600Srdivacky fprintf(stderr, "\033[1m<<< "); 120224145Sdim fprintf(stderr, "%*.*s", (int)len, (int)len, line); 121224145Sdim fprintf(stderr, "\033[m"); 122193326Sed#endif 123193326Sed 124224145Sdim if (!isdigit(line[1]) || !isdigit(line[1]) 125193326Sed || !isdigit(line[2]) || (line[3] != ' ')) { 126245431Sdim _ftp_seterr(0); 127193326Sed return -1; 128208600Srdivacky } 129208600Srdivacky 130224145Sdim err = (line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0'); 131224145Sdim _ftp_seterr(err); 132224145Sdim 133224145Sdim if (e) 134224145Sdim *e = err; 135224145Sdim 136224145Sdim return (line[0] == '2') - 1; 137224145Sdim} 138235633Sdim 139224145Sdim/* 140224145Sdim * Send a command and check reply 141224145Sdim */ 142224145Sdimstatic int 143224145Sdim_ftp_cmd(FILE *f, char *fmt, ...) 144224145Sdim{ 145224145Sdim va_list ap; 146224145Sdim int e; 147224145Sdim 148224145Sdim va_start(ap, fmt); 149224145Sdim vfprintf(f, fmt, ap); 150224145Sdim#ifndef NDEBUG 151224145Sdim fprintf(stderr, "\033[1m>>> "); 152235633Sdim vfprintf(stderr, fmt, ap); 153235633Sdim fprintf(stderr, "\033[m"); 154224145Sdim#endif 155224145Sdim va_end(ap); 156224145Sdim 157224145Sdim _ftp_chkerr(f, &e); 158224145Sdim return e; 159224145Sdim} 160224145Sdim 161224145Sdim/* 162224145Sdim * Transfer file 163224145Sdim */ 164224145Sdimstatic FILE * 165224145Sdim_ftp_transfer(FILE *cf, char *oper, char *file, char *mode, int pasv) 166224145Sdim{ 167245431Sdim struct sockaddr_in sin; 168245431Sdim int sd = -1, l; 169245431Sdim char *s; 170224145Sdim FILE *df; 171245431Sdim 172245431Sdim /* change directory */ 173245431Sdim if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 174224145Sdim *s = 0; 175245431Sdim if (_ftp_cmd(cf, "CWD %s" ENDL, file) != FTP_FILE_ACTION_OK) { 176245431Sdim *s = '/'; 177224145Sdim return NULL; 178245431Sdim } 179245431Sdim *s++ = '/'; 180224145Sdim } else { 181245431Sdim if (_ftp_cmd(cf, "CWD /" ENDL) != FTP_FILE_ACTION_OK) 182245431Sdim return NULL; 183245431Sdim } 184224145Sdim 185245431Sdim /* s now points to file name */ 186245431Sdim 187245431Sdim /* open data socket */ 188245431Sdim if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { 189245431Sdim _fetch_syserr(); 190245431Sdim return NULL; 191245431Sdim } 192245431Sdim 193245431Sdim if (pasv) { 194245431Sdim u_char addr[6]; 195245431Sdim char *ln, *p; 196245431Sdim int i; 197245431Sdim 198245431Sdim /* send PASV command */ 199245431Sdim if (_ftp_cmd(cf, "PASV" ENDL) != FTP_PASSIVE_MODE) 200245431Sdim goto ouch; 201245431Sdim 202245431Sdim /* find address and port number. The reply to the PASV command 203245431Sdim is IMHO the one and only weak point in the FTP protocol. */ 204245431Sdim ln = _ftp_last_reply; 205245431Sdim for (p = ln + 3; !isdigit(*p); p++) 206245431Sdim /* nothing */ ; 207245431Sdim for (p--, i = 0; i < 6; i++) { 208210299Sed p++; /* skip the comma */ 209245431Sdim addr[i] = strtol(p, &p, 10); 210245431Sdim } 211208600Srdivacky 212245431Sdim /* construct sockaddr for data socket */ 213245431Sdim l = sizeof(sin); 214208600Srdivacky if (getpeername(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 215208600Srdivacky goto sysouch; 216208600Srdivacky bcopy(addr, (char *)&sin.sin_addr, 4); 217193326Sed bcopy(addr + 4, (char *)&sin.sin_port, 2); 218224145Sdim 219193326Sed /* connect to data port */ 220198092Srdivacky if (connect(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) 221221345Sdim goto sysouch; 222221345Sdim 223224145Sdim /* make the server initiate the transfer */ 224221345Sdim if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION) 225221345Sdim goto ouch; 226224145Sdim 227193326Sed } else { 228193326Sed u_int32_t a; 229210299Sed u_short p; 230210299Sed int d; 231210299Sed 232224145Sdim /* find our own address, bind, and listen */ 233210299Sed l = sizeof(sin); 234210299Sed if (getsockname(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 235210299Sed goto sysouch; 236210299Sed sin.sin_port = 0; 237210299Sed if (bind(sd, (struct sockaddr *)&sin, l) == -1) 238210299Sed goto sysouch; 239210299Sed if (listen(sd, 1) == -1) 240210299Sed goto sysouch; 241210299Sed 242210299Sed /* find what port we're on and tell the server */ 243210299Sed if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 244210299Sed goto sysouch; 245193326Sed a = ntohl(sin.sin_addr.s_addr); 246224145Sdim p = ntohs(sin.sin_port); 247224145Sdim if (_ftp_cmd(cf, "PORT %d,%d,%d,%d,%d,%d" ENDL, 248224145Sdim (a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff, 249224145Sdim (p >> 8) & 0xff, p & 0xff) != FTP_OK) 250224145Sdim goto ouch; 251224145Sdim 252224145Sdim /* make the server initiate the transfer */ 253224145Sdim if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION) 254224145Sdim goto ouch; 255224145Sdim 256224145Sdim /* accept the incoming connection and go to town */ 257224145Sdim if ((d = accept(sd, NULL, NULL)) == -1) 258224145Sdim goto sysouch; 259224145Sdim close(sd); 260224145Sdim sd = d; 261224145Sdim } 262224145Sdim 263224145Sdim if ((df = fdopen(sd, mode)) == NULL) 264224145Sdim goto sysouch; 265224145Sdim return df; 266224145Sdim 267224145Sdimsysouch: 268224145Sdim _fetch_syserr(); 269224145Sdimouch: 270224145Sdim close(sd); 271224145Sdim return NULL; 272235633Sdim} 273224145Sdim 274224145Sdim/* 275224145Sdim * Log on to FTP server 276224145Sdim */ 277224145Sdimstatic FILE * 278224145Sdim_ftp_connect(char *host, int port, char *user, char *pwd, int verbose) 279224145Sdim{ 280224145Sdim int sd, e, pp = FTP_DEFAULT_PORT; 281224145Sdim char *p, *q; 282224145Sdim FILE *f; 283193326Sed 284193326Sed /* check for proxy */ 285193326Sed if ((p = getenv("FTP_PROXY")) != NULL) { 286193326Sed if ((q = strchr(p, ':')) != NULL) { 287224145Sdim /* XXX check that it's a valid number */ 288224145Sdim pp = atoi(q+1); 289224145Sdim } 290224145Sdim if (q) 291224145Sdim *q = 0; 292224145Sdim sd = fetchConnect(p, pp, verbose); 293224145Sdim if (q) 294224145Sdim *q = ':'; 295210299Sed } else { 296210299Sed /* no proxy, go straight to target */ 297193326Sed sd = fetchConnect(host, port, verbose); 298208600Srdivacky } 299208600Srdivacky 300208600Srdivacky /* check connection */ 301208600Srdivacky if (sd == -1) { 302208600Srdivacky _fetch_syserr(); 303208600Srdivacky return NULL; 304218893Sdim } 305218893Sdim 306218893Sdim /* streams make life easier */ 307218893Sdim if ((f = fdopen(sd, "r+")) == NULL) { 308218893Sdim _fetch_syserr(); 309218893Sdim goto ouch; 310218893Sdim } 311218893Sdim 312218893Sdim /* expect welcome message */ 313218893Sdim if (_ftp_chkerr(f, NULL) == -1) 314218893Sdim goto fouch; 315208600Srdivacky 316208600Srdivacky /* send user name and password */ 317208600Srdivacky if (!user || !*user) 318208600Srdivacky user = FTP_ANONYMOUS_USER; 319210299Sed e = p ? _ftp_cmd(f, "USER %s@%s@%d" ENDL, user, host, port) 320210299Sed : _ftp_cmd(f, "USER %s" ENDL, user); 321210299Sed 322210299Sed /* did the server request a password? */ 323210299Sed if (e == FTP_NEED_PASSWORD) { 324210299Sed if (!pwd || !*pwd) 325210299Sed pwd = FTP_ANONYMOUS_PASSWORD; 326210299Sed e = _ftp_cmd(f, "PASS %s" ENDL, pwd); 327210299Sed } 328208600Srdivacky 329193326Sed /* did the server request an account? */ 330193326Sed if (e == FTP_NEED_ACCOUNT) 331193326Sed /* help! */ ; 332193326Sed 333208600Srdivacky /* we should be done by now */ 334208600Srdivacky if (e != FTP_LOGGED_IN) 335245431Sdim goto fouch; 336245431Sdim 337245431Sdim /* might as well select mode and type at once */ 338245431Sdim#ifdef FTP_FORCE_STREAM_MODE 339245431Sdim if (_ftp_cmd(f, "MODE S" ENDL) != FTP_OK) /* default is S */ 340245431Sdim goto ouch; 341245431Sdim#endif 342245431Sdim if (_ftp_cmd(f, "TYPE I" ENDL) != FTP_OK) /* default is A */ 343245431Sdim goto ouch; 344245431Sdim 345245431Sdim /* done */ 346245431Sdim return f; 347245431Sdim 348245431Sdimouch: 349245431Sdim close(sd); 350245431Sdim return NULL; 351245431Sdimfouch: 352245431Sdim fclose(f); 353245431Sdim return NULL; 354245431Sdim} 355245431Sdim 356245431Sdim/* 357245431Sdim * Disconnect from server 358208600Srdivacky */ 359208600Srdivackystatic void 360193326Sed_ftp_disconnect(FILE *f) 361198092Srdivacky{ 362218893Sdim _ftp_cmd(f, "QUIT" ENDL); 363210299Sed fclose(f); 364210299Sed} 365210299Sed 366210299Sed/* 367210299Sed * Check if we're already connected 368193326Sed */ 369193326Sedstatic int 370198092Srdivacky_ftp_isconnected(struct url *url) 371212904Sdim{ 372212904Sdim return (cached_socket 373212904Sdim && (strcmp(url->host, cached_host.host) == 0) 374212904Sdim && (strcmp(url->user, cached_host.user) == 0) 375212904Sdim && (strcmp(url->pwd, cached_host.pwd) == 0) 376218893Sdim && (url->port == cached_host.port)); 377218893Sdim} 378218893Sdim 379218893Sdim/* 380218893Sdim * FTP session 381218893Sdim */ 382212904Sdimstatic FILE * 383212904SdimfetchXxxFTP(struct url *url, char *oper, char *mode, char *flags) 384212904Sdim{ 385212904Sdim FILE *cf = NULL; 386212904Sdim int e; 387212904Sdim 388212904Sdim /* set default port */ 389193326Sed if (!url->port) 390193326Sed url->port = FTP_DEFAULT_PORT; 391193326Sed 392204643Srdivacky /* try to use previously cached connection; there should be a 226 waiting */ 393204643Srdivacky if (_ftp_isconnected(url)) { 394193326Sed _ftp_chkerr(cached_socket, &e); 395210299Sed if (e > 0) 396210299Sed cf = cached_socket; 397193326Sed } 398193326Sed 399193326Sed /* connect to server */ 400193326Sed if (!cf) { 401193326Sed cf = _ftp_connect(url->host, url->port, url->user, url->pwd, 402224145Sdim (strchr(flags, 'v') != NULL)); 403193326Sed if (!cf) 404208600Srdivacky return NULL; 405208600Srdivacky if (cached_socket) 406208600Srdivacky _ftp_disconnect(cached_socket); 407193326Sed cached_socket = cf; 408193326Sed memcpy(&cached_host, url, sizeof(struct url)); 409224145Sdim } 410193326Sed 411193326Sed /* initiate the transfer */ 412207619Srdivacky return _ftp_transfer(cf, oper, url->doc, mode, (flags && strchr(flags, 'p'))); 413207619Srdivacky} 414193326Sed 415193326Sed/* 416208600Srdivacky * Itsy bitsy teeny weenie 417193326Sed */ 418198092SrdivackyFILE * 419224145SdimfetchGetFTP(struct url *url, char *flags) 420224145Sdim{ 421193326Sed return fetchXxxFTP(url, "RETR", "r", flags); 422198092Srdivacky} 423193326Sed 424193326SedFILE * 425193326SedfetchPutFTP(struct url *url, char *flags) 426193326Sed{ 427208600Srdivacky if (flags && strchr(flags, 'a')) 428193326Sed return fetchXxxFTP(url, "APPE", "w", flags); 429224145Sdim else return fetchXxxFTP(url, "STOR", "w", flags); 430193326Sed} 431198092Srdivacky 432193326Sedextern void warnx(char *fmt, ...); 433193326Sedint 434224145SdimfetchStatFTP(struct url *url, struct url_stat *us, char *flags) 435224145Sdim{ 436193326Sed warnx("fetchStatFTP(): not implemented"); 437193326Sed return -1; 438193326Sed} 439193326Sed 440193326Sed