ftp.c revision 41862
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.7 1998/11/06 22:14:08 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/param.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.h" 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 struct url 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 int err; 105 106 if (e) 107 *e = 0; 108 109 do { 110 if (((line = fgetln(s, &len)) == NULL) || (len < 4)) { 111 _fetch_syserr(); 112 return -1; 113 } 114 } while (line[3] == '-'); 115 116 _ftp_last_reply = line; 117 118#ifndef NDEBUG 119 fprintf(stderr, "\033[1m<<< "); 120 fprintf(stderr, "%*.*s", (int)len, (int)len, line); 121 fprintf(stderr, "\033[m"); 122#endif 123 124 if (!isdigit(line[1]) || !isdigit(line[1]) 125 || !isdigit(line[2]) || (line[3] != ' ')) { 126 _ftp_seterr(0); 127 return -1; 128 } 129 130 err = (line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0'); 131 _ftp_seterr(err); 132 133 if (e) 134 *e = err; 135 136 return (line[0] == '2') - 1; 137} 138 139/* 140 * Send a command and check reply 141 */ 142static int 143_ftp_cmd(FILE *f, char *fmt, ...) 144{ 145 va_list ap; 146 int e; 147 148 va_start(ap, fmt); 149 vfprintf(f, fmt, ap); 150#ifndef NDEBUG 151 fprintf(stderr, "\033[1m>>> "); 152 vfprintf(stderr, fmt, ap); 153 fprintf(stderr, "\033[m"); 154#endif 155 va_end(ap); 156 157 _ftp_chkerr(f, &e); 158 return e; 159} 160 161/* 162 * Transfer file 163 */ 164static FILE * 165_ftp_transfer(FILE *cf, char *oper, char *file, char *mode, int pasv) 166{ 167 struct sockaddr_in sin; 168 int sd = -1, l; 169 char *s; 170 FILE *df; 171 172 /* change directory */ 173 if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 174 *s = 0; 175 if (_ftp_cmd(cf, "CWD %s" ENDL, file) != FTP_FILE_ACTION_OK) { 176 *s = '/'; 177 return NULL; 178 } 179 *s++ = '/'; 180 } else { 181 if (_ftp_cmd(cf, "CWD /" ENDL) != FTP_FILE_ACTION_OK) 182 return NULL; 183 } 184 185 /* s now points to file name */ 186 187 /* open data socket */ 188 if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { 189 _fetch_syserr(); 190 return NULL; 191 } 192 193 if (pasv) { 194 u_char addr[6]; 195 char *ln, *p; 196 int i; 197 198 /* send PASV command */ 199 if (_ftp_cmd(cf, "PASV" ENDL) != FTP_PASSIVE_MODE) 200 goto ouch; 201 202 /* find address and port number. The reply to the PASV command 203 is IMHO the one and only weak point in the FTP protocol. */ 204 ln = _ftp_last_reply; 205 for (p = ln + 3; !isdigit(*p); p++) 206 /* nothing */ ; 207 for (p--, i = 0; i < 6; i++) { 208 p++; /* skip the comma */ 209 addr[i] = strtol(p, &p, 10); 210 } 211 212 /* construct sockaddr for data socket */ 213 l = sizeof(sin); 214 if (getpeername(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 215 goto sysouch; 216 bcopy(addr, (char *)&sin.sin_addr, 4); 217 bcopy(addr + 4, (char *)&sin.sin_port, 2); 218 219 /* connect to data port */ 220 if (connect(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) 221 goto sysouch; 222 223 /* make the server initiate the transfer */ 224 if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION) 225 goto ouch; 226 227 } else { 228 u_int32_t a; 229 u_short p; 230 int d; 231 232 /* find our own address, bind, and listen */ 233 l = sizeof(sin); 234 if (getsockname(fileno(cf), (struct sockaddr *)&sin, &l) == -1) 235 goto sysouch; 236 sin.sin_port = 0; 237 if (bind(sd, (struct sockaddr *)&sin, l) == -1) 238 goto sysouch; 239 if (listen(sd, 1) == -1) 240 goto sysouch; 241 242 /* find what port we're on and tell the server */ 243 if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 244 goto sysouch; 245 a = ntohl(sin.sin_addr.s_addr); 246 p = ntohs(sin.sin_port); 247 if (_ftp_cmd(cf, "PORT %d,%d,%d,%d,%d,%d" ENDL, 248 (a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff, 249 (p >> 8) & 0xff, p & 0xff) != FTP_OK) 250 goto ouch; 251 252 /* make the server initiate the transfer */ 253 if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION) 254 goto ouch; 255 256 /* accept the incoming connection and go to town */ 257 if ((d = accept(sd, NULL, NULL)) == -1) 258 goto sysouch; 259 close(sd); 260 sd = d; 261 } 262 263 if ((df = fdopen(sd, mode)) == NULL) 264 goto sysouch; 265 return df; 266 267sysouch: 268 _fetch_syserr(); 269ouch: 270 close(sd); 271 return NULL; 272} 273 274/* 275 * Log on to FTP server 276 */ 277static FILE * 278_ftp_connect(char *host, int port, char *user, char *pwd, int verbose) 279{ 280 int sd, e, pp = FTP_DEFAULT_PORT; 281 char *p, *q; 282 FILE *f; 283 284 /* check for proxy */ 285 if ((p = getenv("FTP_PROXY")) != NULL) { 286 if ((q = strchr(p, ':')) != NULL) { 287 /* XXX check that it's a valid number */ 288 pp = atoi(q+1); 289 } 290 if (q) 291 *q = 0; 292 sd = fetchConnect(p, pp, verbose); 293 if (q) 294 *q = ':'; 295 } else { 296 /* no proxy, go straight to target */ 297 sd = fetchConnect(host, port, verbose); 298 } 299 300 /* check connection */ 301 if (sd == -1) { 302 _fetch_syserr(); 303 return NULL; 304 } 305 306 /* streams make life easier */ 307 if ((f = fdopen(sd, "r+")) == NULL) { 308 _fetch_syserr(); 309 goto ouch; 310 } 311 312 /* expect welcome message */ 313 if (_ftp_chkerr(f, NULL) == -1) 314 goto fouch; 315 316 /* send user name and password */ 317 if (!user || !*user) 318 user = FTP_ANONYMOUS_USER; 319 e = p ? _ftp_cmd(f, "USER %s@%s@%d" ENDL, user, host, port) 320 : _ftp_cmd(f, "USER %s" ENDL, user); 321 322 /* did the server request a password? */ 323 if (e == FTP_NEED_PASSWORD) { 324 if (!pwd || !*pwd) 325 pwd = FTP_ANONYMOUS_PASSWORD; 326 e = _ftp_cmd(f, "PASS %s" ENDL, pwd); 327 } 328 329 /* did the server request an account? */ 330 if (e == FTP_NEED_ACCOUNT) 331 /* help! */ ; 332 333 /* we should be done by now */ 334 if (e != FTP_LOGGED_IN) 335 goto fouch; 336 337 /* might as well select mode and type at once */ 338#ifdef FTP_FORCE_STREAM_MODE 339 if (_ftp_cmd(f, "MODE S" ENDL) != FTP_OK) /* default is S */ 340 goto ouch; 341#endif 342 if (_ftp_cmd(f, "TYPE I" ENDL) != FTP_OK) /* default is A */ 343 goto ouch; 344 345 /* done */ 346 return f; 347 348ouch: 349 close(sd); 350 return NULL; 351fouch: 352 fclose(f); 353 return NULL; 354} 355 356/* 357 * Disconnect from server 358 */ 359static void 360_ftp_disconnect(FILE *f) 361{ 362 _ftp_cmd(f, "QUIT" ENDL); 363 fclose(f); 364} 365 366/* 367 * Check if we're already connected 368 */ 369static int 370_ftp_isconnected(struct url *url) 371{ 372 return (cached_socket 373 && (strcmp(url->host, cached_host.host) == 0) 374 && (strcmp(url->user, cached_host.user) == 0) 375 && (strcmp(url->pwd, cached_host.pwd) == 0) 376 && (url->port == cached_host.port)); 377} 378 379/* 380 * FTP session 381 */ 382static FILE * 383fetchXxxFTP(struct url *url, char *oper, char *mode, char *flags) 384{ 385 FILE *cf = NULL; 386 int e; 387 388 /* set default port */ 389 if (!url->port) 390 url->port = FTP_DEFAULT_PORT; 391 392 /* try to use previously cached connection; there should be a 226 waiting */ 393 if (_ftp_isconnected(url)) { 394 _ftp_chkerr(cached_socket, &e); 395 if (e > 0) 396 cf = cached_socket; 397 } 398 399 /* connect to server */ 400 if (!cf) { 401 cf = _ftp_connect(url->host, url->port, url->user, url->pwd, 402 (strchr(flags, 'v') != NULL)); 403 if (!cf) 404 return NULL; 405 if (cached_socket) 406 _ftp_disconnect(cached_socket); 407 cached_socket = cf; 408 memcpy(&cached_host, url, sizeof(struct url)); 409 } 410 411 /* initiate the transfer */ 412 return _ftp_transfer(cf, oper, url->doc, mode, (flags && strchr(flags, 'p'))); 413} 414 415/* 416 * Itsy bitsy teeny weenie 417 */ 418FILE * 419fetchGetFTP(struct url *url, char *flags) 420{ 421 return fetchXxxFTP(url, "RETR", "r", flags); 422} 423 424FILE * 425fetchPutFTP(struct url *url, char *flags) 426{ 427 if (flags && strchr(flags, 'a')) 428 return fetchXxxFTP(url, "APPE", "w", flags); 429 else return fetchXxxFTP(url, "STOR", "w", flags); 430} 431 432extern void warnx(char *fmt, ...); 433int 434fetchStatFTP(struct url *url, struct url_stat *us, char *flags) 435{ 436 warnx("fetchStatFTP(): not implemented"); 437 return -1; 438} 439 440