ftp.c revision 40939
1/*- 2 * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer 10 * in this position and unchanged. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 * 28 * $Id: ftp.c,v 1.5 1998/08/17 09:30:19 des Exp $ 29 */ 30 31/* 32 * Portions of this code were taken from or based on ftpio.c: 33 * 34 * ---------------------------------------------------------------------------- 35 * "THE BEER-WARE LICENSE" (Revision 42): 36 * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you 37 * can do whatever you want with this stuff. If we meet some day, and you think 38 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 39 * ---------------------------------------------------------------------------- 40 * 41 * Major Changelog: 42 * 43 * Dag-Erling Co�dan Sm�rgrav 44 * 9 Jun 1998 45 * 46 * Incorporated into libfetch 47 * 48 * Jordan K. Hubbard 49 * 17 Jan 1996 50 * 51 * Turned inside out. Now returns xfers as new file ids, not as a special 52 * `state' of FTP_t 53 * 54 * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $ 55 * 56 */ 57 58#include <sys/types.h> 59#include <sys/socket.h> 60#include <netinet/in.h> 61#include <sys/errno.h> 62 63#include <ctype.h> 64#include <errno.h> 65#include <netdb.h> 66#include <stdarg.h> 67#include <stdio.h> 68#include <stdlib.h> 69#include <string.h> 70#include <unistd.h> 71 72#include "fetch.h" 73#include "common.h" 74#include "ftperr.c" 75 76#define FTP_DEFAULT_TO_ANONYMOUS 77#define FTP_ANONYMOUS_USER "ftp" 78#define FTP_ANONYMOUS_PASSWORD "ftp" 79#define FTP_DEFAULT_PORT 21 80 81#define FTP_OPEN_DATA_CONNECTION 150 82#define FTP_OK 200 83#define FTP_PASSIVE_MODE 227 84#define FTP_LOGGED_IN 230 85#define FTP_FILE_ACTION_OK 250 86#define FTP_NEED_PASSWORD 331 87#define FTP_NEED_ACCOUNT 332 88 89#define ENDL "\r\n" 90 91static url_t cached_host; 92static FILE *cached_socket; 93 94static char *_ftp_last_reply; 95 96/* 97 * Get server response, check that first digit is a '2' 98 */ 99static int 100_ftp_chkerr(FILE *s, int *e) 101{ 102 char *line; 103 size_t len; 104 105 if (e) 106 *e = 0; 107 108 do { 109 if (((line = fgetln(s, &len)) == NULL) || (len < 4)) { 110 _fetch_syserr(); 111 return -1; 112 } 113 } while (line[3] == '-'); 114 115 _ftp_last_reply = line; 116 117#ifndef NDEBUG 118 fprintf(stderr, "\033[1m<<< "); 119 fprintf(stderr, "%*.*s", (int)len, (int)len, line); 120 fprintf(stderr, "\033[m"); 121#endif 122 123 if (!isdigit(line[1]) || !isdigit(line[1]) 124 || !isdigit(line[2]) || (line[3] != ' ')) { 125 _ftp_seterr(-1); 126 return -1; 127 } 128 129 _ftp_seterr((line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0')); 130 131 if (e) 132 *e = fetchLastErrCode; 133 134 return (line[0] == '2') - 1; 135} 136 137/* 138 * Send a command and check reply 139 */ 140static int 141_ftp_cmd(FILE *f, char *fmt, ...) 142{ 143 va_list ap; 144 int e; 145 146 va_start(ap, fmt); 147 vfprintf(f, fmt, ap); 148#ifndef NDEBUG 149 fprintf(stderr, "\033[1m>>> "); 150 vfprintf(stderr, fmt, ap); 151 fprintf(stderr, "\033[m"); 152#endif 153 va_end(ap); 154 155 _ftp_chkerr(f, &e); 156 return e; 157} 158 159/* 160 * Transfer file 161 */ 162static FILE * 163_ftp_transfer(FILE *cf, char *oper, char *file, char *mode, int pasv) 164{ 165 struct sockaddr_in sin; 166 int sd = -1, l; 167 char *s; 168 FILE *df; 169 170 /* change directory */ 171 if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 172 *s = 0; 173 if (_ftp_cmd(cf, "CWD %s" ENDL, file) != FTP_FILE_ACTION_OK) { 174 *s = '/'; 175 return NULL; 176 } 177 *s++ = '/'; 178 } else { 179 if (_ftp_cmd(cf, "CWD /" ENDL) != FTP_FILE_ACTION_OK) 180 return NULL; 181 } 182 183 /* s now points to file name */ 184 185 /* open data socket */ 186 if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { 187 _fetch_syserr(); 188 return NULL; 189 } 190 191 if (pasv) { 192 u_char addr[6]; 193 char *ln, *p; 194 int i; 195 196 /* send PASV command */ 197 if (_ftp_cmd(cf, "PASV" ENDL) != FTP_PASSIVE_MODE) 198 goto ouch; 199 200 /* find address and port number. The reply to the PASV command 201 is IMHO the one and only weak point in the FTP protocol. */ 202 ln = _ftp_last_reply; 203 for (p = ln + 3; !isdigit(*p); p++) 204 /* nothing */ ; 205 for (p--, i = 0; i < 6; i++) { 206 p++; /* skip the comma */ 207 addr[i] = strtol(p, &p, 10); 208 } 209 210 /* construct sockaddr for data socket */ 211 l = sizeof(sin); 212 if (getpeername(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 213 goto sysouch; 214 bcopy(addr, (char *)&sin.sin_addr, 4); 215 bcopy(addr + 4, (char *)&sin.sin_port, 2); 216 217 /* connect to data port */ 218 if (connect(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) 219 goto sysouch; 220 221 /* make the server initiate the transfer */ 222 if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION) 223 goto ouch; 224 225 } else { 226 u_int32_t a; 227 u_short p; 228 int d; 229 230 /* find our own address, bind, and listen */ 231 l = sizeof(sin); 232 if (getsockname(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 233 goto sysouch; 234 sin.sin_port = 0; 235 if (bind(sd, (struct sockaddr *)&sin, l) == -1) 236 goto sysouch; 237 if (listen(sd, 1) == -1) 238 goto sysouch; 239 240 /* find what port we're on and tell the server */ 241 if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 242 goto sysouch; 243 a = ntohl(sin.sin_addr.s_addr); 244 p = ntohs(sin.sin_port); 245 if (_ftp_cmd(cf, "PORT %d,%d,%d,%d,%d,%d" ENDL, 246 (a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff, 247 (p >> 8) & 0xff, p & 0xff) != FTP_OK) 248 goto ouch; 249 250 /* make the server initiate the transfer */ 251 if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION) 252 goto ouch; 253 254 /* accept the incoming connection and go to town */ 255 if ((d = accept(sd, NULL, NULL)) == -1) 256 goto sysouch; 257 close(sd); 258 sd = d; 259 } 260 261 if ((df = fdopen(sd, mode)) == NULL) 262 goto sysouch; 263 return df; 264 265sysouch: 266 _fetch_syserr(); 267ouch: 268 close(sd); 269 return NULL; 270} 271 272/* 273 * Log on to FTP server 274 */ 275static FILE * 276_ftp_connect(char *host, int port, char *user, char *pwd) 277{ 278 int sd, e, pp = FTP_DEFAULT_PORT; 279 char *p, *q; 280 FILE *f; 281 282 /* check for proxy */ 283 if ((p = getenv("FTP_PROXY")) != NULL) { 284 if ((q = strchr(p, ':')) != NULL) { 285 /* XXX check that it's a valid number */ 286 pp = atoi(q+1); 287 } 288 if (q) 289 *q = 0; 290 sd = fetchConnect(p, pp); 291 if (q) 292 *q = ':'; 293 } else { 294 /* no proxy, go straight to target */ 295 sd = fetchConnect(host, port); 296 } 297 298 /* check connection */ 299 if (sd == -1) { 300 _fetch_syserr(); 301 return NULL; 302 } 303 304 /* streams make life easier */ 305 if ((f = fdopen(sd, "r+")) == NULL) { 306 _fetch_syserr(); 307 goto ouch; 308 } 309 310 /* expect welcome message */ 311 if (_ftp_chkerr(f, NULL) == -1) 312 goto fouch; 313 314 /* send user name and password */ 315 if (!user || !*user) 316 user = FTP_ANONYMOUS_USER; 317 e = p ? _ftp_cmd(f, "USER %s@%s@%d" ENDL, user, host, port) 318 : _ftp_cmd(f, "USER %s" ENDL, user); 319 320 /* did the server request a password? */ 321 if (e == FTP_NEED_PASSWORD) { 322 if (!pwd || !*pwd) 323 pwd = FTP_ANONYMOUS_PASSWORD; 324 e = _ftp_cmd(f, "PASS %s" ENDL, pwd); 325 } 326 327 /* did the server request an account? */ 328 if (e == FTP_NEED_ACCOUNT) 329 /* help! */ ; 330 331 /* we should be done by now */ 332 if (e != FTP_LOGGED_IN) 333 goto fouch; 334 335 /* might as well select mode and type at once */ 336#ifdef FTP_FORCE_STREAM_MODE 337 if (_ftp_cmd(f, "MODE S" ENDL) != FTP_OK) /* default is S */ 338 goto ouch; 339#endif 340 if (_ftp_cmd(f, "TYPE I" ENDL) != FTP_OK) /* default is A */ 341 goto ouch; 342 343 /* done */ 344 return f; 345 346ouch: 347 close(sd); 348 return NULL; 349fouch: 350 fclose(f); 351 return NULL; 352} 353 354/* 355 * Disconnect from server 356 */ 357static void 358_ftp_disconnect(FILE *f) 359{ 360 _ftp_cmd(f, "QUIT" ENDL); 361 fclose(f); 362} 363 364/* 365 * Check if we're already connected 366 */ 367static int 368_ftp_isconnected(url_t *url) 369{ 370 return (cached_socket 371 && (strcmp(url->host, cached_host.host) == 0) 372 && (strcmp(url->user, cached_host.user) == 0) 373 && (strcmp(url->pwd, cached_host.pwd) == 0) 374 && (url->port == cached_host.port)); 375} 376 377/* 378 * FTP session 379 */ 380static FILE * 381fetchXxxFTP(url_t *url, char *oper, char *mode, char *flags) 382{ 383 FILE *cf = NULL; 384 int e; 385 386 /* set default port */ 387 if (!url->port) 388 url->port = FTP_DEFAULT_PORT; 389 390 /* try to use previously cached connection; there should be a 226 waiting */ 391 if (_ftp_isconnected(url)) { 392 _ftp_chkerr(cached_socket, &e); 393 if (e > 0) 394 cf = cached_socket; 395 } 396 397 /* connect to server */ 398 if (!cf) { 399 cf = _ftp_connect(url->host, url->port, url->user, url->pwd); 400 if (!cf) 401 return NULL; 402 if (cached_socket) 403 _ftp_disconnect(cached_socket); 404 cached_socket = cf; 405 memcpy(&cached_host, url, sizeof(url_t)); 406 } 407 408 /* initiate the transfer */ 409 return _ftp_transfer(cf, oper, url->doc, mode, (flags && strchr(flags, 'p'))); 410} 411 412/* 413 * Itsy bitsy teeny weenie 414 */ 415FILE * 416fetchGetFTP(url_t *url, char *flags) 417{ 418 return fetchXxxFTP(url, "RETR", "r", flags); 419} 420 421FILE * 422fetchPutFTP(url_t *url, char *flags) 423{ 424 if (flags && strchr(flags, 'a')) 425 return fetchXxxFTP(url, "APPE", "w", flags); 426 else return fetchXxxFTP(url, "STOR", "w", flags); 427} 428