ftp.c revision 60707
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 * $FreeBSD: head/lib/libfetch/ftp.c 60707 2000-05-19 09:45:42Z des $ 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 <sys/uio.h> 61#include <netinet/in.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 <time.h> 71#include <unistd.h> 72 73#include "fetch.h" 74#include "common.h" 75#include "ftperr.h" 76 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_FILE_STATUS 213 84#define FTP_SERVICE_READY 220 85#define FTP_PASSIVE_MODE 227 86#define FTP_LOGGED_IN 230 87#define FTP_FILE_ACTION_OK 250 88#define FTP_NEED_PASSWORD 331 89#define FTP_NEED_ACCOUNT 332 90#define FTP_FILE_OK 350 91#define FTP_SYNTAX_ERROR 500 92 93static char ENDL[2] = "\r\n"; 94 95static struct url cached_host; 96static int cached_socket; 97 98static char *last_reply; 99static size_t lr_size, lr_length; 100static int last_code; 101 102#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 103 && isdigit(foo[2]) \ 104 && (foo[3] == ' ' || foo[3] == '\0')) 105#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 106 && isdigit(foo[2]) && foo[3] == '-') 107 108/* 109 * Get server response 110 */ 111static int 112_ftp_chkerr(int cd) 113{ 114 do { 115 if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 116 _fetch_syserr(); 117 return -1; 118 } 119#ifndef NDEBUG 120 _fetch_info("got reply '%.*s'", lr_length - 2, last_reply); 121#endif 122 } while (isftpinfo(last_reply)); 123 124 while (lr_length && isspace(last_reply[lr_length-1])) 125 lr_length--; 126 last_reply[lr_length] = 0; 127 128 if (!isftpreply(last_reply)) { 129 _ftp_seterr(999); 130 return -1; 131 } 132 133 last_code = (last_reply[0] - '0') * 100 134 + (last_reply[1] - '0') * 10 135 + (last_reply[2] - '0'); 136 137 return last_code; 138} 139 140/* 141 * Send a command and check reply 142 */ 143static int 144_ftp_cmd(int cd, char *fmt, ...) 145{ 146 va_list ap; 147 struct iovec iov[2]; 148 char *msg; 149 int r; 150 151 va_start(ap, fmt); 152 vasprintf(&msg, fmt, ap); 153 va_end(ap); 154 155 if (msg == NULL) { 156 errno = ENOMEM; 157 _fetch_syserr(); 158 return -1; 159 } 160#ifndef NDEBUG 161 _fetch_info("sending '%s'", msg); 162#endif 163 iov[0].iov_base = msg; 164 iov[0].iov_len = strlen(msg); 165 iov[1].iov_base = ENDL; 166 iov[1].iov_len = sizeof ENDL; 167 r = writev(cd, iov, 2); 168 free(msg); 169 if (r == -1) { 170 _fetch_syserr(); 171 return -1; 172 } 173 174 return _ftp_chkerr(cd); 175} 176 177/* 178 * Transfer file 179 */ 180static FILE * 181_ftp_transfer(int cd, char *oper, char *file, 182 char *mode, off_t offset, char *flags) 183{ 184 struct sockaddr_in sin; 185 int pasv, high, verbose; 186 int e, sd = -1; 187 socklen_t l; 188 char *s; 189 FILE *df; 190 191 /* check flags */ 192 pasv = (flags && strchr(flags, 'p')); 193 high = (flags && strchr(flags, 'h')); 194 verbose = (flags && strchr(flags, 'v')); 195 196 /* change directory */ 197 if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 198 *s = 0; 199 if (verbose) 200 _fetch_info("changing directory to %s", file); 201 if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) { 202 *s = '/'; 203 if (e != -1) 204 _ftp_seterr(e); 205 return NULL; 206 } 207 *s++ = '/'; 208 } else { 209 if (verbose) 210 _fetch_info("changing directory to /"); 211 if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) { 212 if (e != -1) 213 _ftp_seterr(e); 214 return NULL; 215 } 216 } 217 218 /* s now points to file name */ 219 220 /* open data socket */ 221 if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { 222 _fetch_syserr(); 223 return NULL; 224 } 225 226 if (pasv) { 227 u_char addr[6]; 228 char *ln, *p; 229 int i; 230 231 /* send PASV command */ 232 if (verbose) 233 _fetch_info("setting passive mode"); 234 if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 235 goto ouch; 236 237 /* 238 * Find address and port number. The reply to the PASV command 239 * is IMHO the one and only weak point in the FTP protocol. 240 */ 241 ln = last_reply; 242 for (p = ln + 3; *p && !isdigit(*p); p++) 243 /* nothing */ ; 244 for (i = 0; *p, i < 6; i++, p++) 245 addr[i] = strtol(p, &p, 10); 246 if (i < 6) { 247 e = 999; 248 goto ouch; 249 } 250 251 /* seek to required offset */ 252 if (offset) 253 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 254 goto sysouch; 255 256 /* construct sockaddr for data socket */ 257 l = sizeof sin; 258 if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 259 goto sysouch; 260 bcopy(addr, (char *)&sin.sin_addr, 4); 261 bcopy(addr + 4, (char *)&sin.sin_port, 2); 262 263 /* connect to data port */ 264 if (verbose) 265 _fetch_info("opening data connection"); 266 if (connect(sd, (struct sockaddr *)&sin, sizeof sin) == -1) 267 goto sysouch; 268 269 /* make the server initiate the transfer */ 270 if (verbose) 271 _fetch_info("initiating transfer"); 272 e = _ftp_cmd(cd, "%s %s", oper, s); 273 if (e != FTP_OPEN_DATA_CONNECTION) 274 goto ouch; 275 276 } else { 277 u_int32_t a; 278 u_short p; 279 int arg, d; 280 281 /* find our own address, bind, and listen */ 282 l = sizeof sin; 283 if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 284 goto sysouch; 285 sin.sin_port = 0; 286 arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; 287 if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 288 (char *)&arg, sizeof arg) == -1) 289 goto sysouch; 290 if (verbose) 291 _fetch_info("binding data socket"); 292 if (bind(sd, (struct sockaddr *)&sin, l) == -1) 293 goto sysouch; 294 if (listen(sd, 1) == -1) 295 goto sysouch; 296 297 /* find what port we're on and tell the server */ 298 if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 299 goto sysouch; 300 a = ntohl(sin.sin_addr.s_addr); 301 p = ntohs(sin.sin_port); 302 e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 303 (a >> 24) & 0xff, (a >> 16) & 0xff, 304 (a >> 8) & 0xff, a & 0xff, 305 (p >> 8) & 0xff, p & 0xff); 306 if (e != FTP_OK) 307 goto ouch; 308 309 /* make the server initiate the transfer */ 310 if (verbose) 311 _fetch_info("initiating transfer"); 312 e = _ftp_cmd(cd, "%s %s", oper, s); 313 if (e != FTP_OPEN_DATA_CONNECTION) 314 goto ouch; 315 316 /* accept the incoming connection and go to town */ 317 if ((d = accept(sd, NULL, NULL)) == -1) 318 goto sysouch; 319 close(sd); 320 sd = d; 321 } 322 323 if ((df = fdopen(sd, mode)) == NULL) 324 goto sysouch; 325 return df; 326 327sysouch: 328 _fetch_syserr(); 329 close(sd); 330 return NULL; 331 332ouch: 333 if (e != -1) 334 _ftp_seterr(e); 335 close(sd); 336 return NULL; 337} 338 339/* 340 * Log on to FTP server 341 */ 342static int 343_ftp_connect(char *host, int port, char *user, char *pwd, char *flags) 344{ 345 int cd, e, pp = 0, direct, verbose; 346 char *p, *q; 347 348 direct = (flags && strchr(flags, 'd')); 349 verbose = (flags && strchr(flags, 'v')); 350 351 /* check for proxy */ 352 if (!direct && (p = getenv("FTP_PROXY")) != NULL) { 353 if ((q = strchr(p, ':')) != NULL) { 354 if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) { 355 /* XXX we should emit some kind of warning */ 356 } 357 pp = atoi(q+1); 358 if (pp < 1 || pp > 65535) { 359 /* XXX we should emit some kind of warning */ 360 } 361 } 362 if (!pp) { 363 struct servent *se; 364 365 if ((se = getservbyname("ftp", "tcp")) != NULL) 366 pp = ntohs(se->s_port); 367 else 368 pp = FTP_DEFAULT_PORT; 369 } 370 if (q) 371 *q = 0; 372 cd = _fetch_connect(p, pp, verbose); 373 if (q) 374 *q = ':'; 375 } else { 376 /* no proxy, go straight to target */ 377 cd = _fetch_connect(host, port, verbose); 378 p = NULL; 379 } 380 381 /* check connection */ 382 if (cd == -1) { 383 _fetch_syserr(); 384 return NULL; 385 } 386 387 /* expect welcome message */ 388 if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 389 goto fouch; 390 391 /* send user name and password */ 392 if (!user || !*user) 393 user = FTP_ANONYMOUS_USER; 394 e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port) 395 : _ftp_cmd(cd, "USER %s", user); 396 397 /* did the server request a password? */ 398 if (e == FTP_NEED_PASSWORD) { 399 if (!pwd || !*pwd) 400 pwd = FTP_ANONYMOUS_PASSWORD; 401 e = _ftp_cmd(cd, "PASS %s", pwd); 402 } 403 404 /* did the server request an account? */ 405 if (e == FTP_NEED_ACCOUNT) 406 goto fouch; 407 408 /* we should be done by now */ 409 if (e != FTP_LOGGED_IN) 410 goto fouch; 411 412 /* might as well select mode and type at once */ 413#ifdef FTP_FORCE_STREAM_MODE 414 if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 415 goto fouch; 416#endif 417 if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 418 goto fouch; 419 420 /* done */ 421 return cd; 422 423fouch: 424 if (e != -1) 425 _ftp_seterr(e); 426 close(cd); 427 return NULL; 428} 429 430/* 431 * Disconnect from server 432 */ 433static void 434_ftp_disconnect(int cd) 435{ 436 (void)_ftp_cmd(cd, "QUIT"); 437 close(cd); 438} 439 440/* 441 * Check if we're already connected 442 */ 443static int 444_ftp_isconnected(struct url *url) 445{ 446 return (cached_socket 447 && (strcmp(url->host, cached_host.host) == 0) 448 && (strcmp(url->user, cached_host.user) == 0) 449 && (strcmp(url->pwd, cached_host.pwd) == 0) 450 && (url->port == cached_host.port)); 451} 452 453/* 454 * Check the cache, reconnect if no luck 455 */ 456static int 457_ftp_cached_connect(struct url *url, char *flags) 458{ 459 int e, cd; 460 461 cd = -1; 462 463 /* set default port */ 464 if (!url->port) { 465 struct servent *se; 466 467 if ((se = getservbyname("ftp", "tcp")) != NULL) 468 url->port = ntohs(se->s_port); 469 else 470 url->port = FTP_DEFAULT_PORT; 471 } 472 473 /* try to use previously cached connection */ 474 if (_ftp_isconnected(url)) { 475 e = _ftp_cmd(cached_socket, "NOOP"); 476 if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 477 cd = cached_socket; 478 } 479 480 /* connect to server */ 481 if (cd == -1) { 482 cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags); 483 if (cd == -1) 484 return -1; 485 if (cached_socket) 486 _ftp_disconnect(cached_socket); 487 cached_socket = cd; 488 memcpy(&cached_host, url, sizeof *url); 489 } 490 491 return cd; 492} 493 494/* 495 * Get file 496 */ 497FILE * 498fetchGetFTP(struct url *url, char *flags) 499{ 500 int cd; 501 502 /* connect to server */ 503 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 504 return NULL; 505 506 /* initiate the transfer */ 507 return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags); 508} 509 510/* 511 * Put file 512 */ 513FILE * 514fetchPutFTP(struct url *url, char *flags) 515{ 516 int cd; 517 518 /* connect to server */ 519 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 520 return NULL; 521 522 /* initiate the transfer */ 523 return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR", 524 url->doc, "w", url->offset, flags); 525} 526 527/* 528 * Get file stats 529 */ 530int 531fetchStatFTP(struct url *url, struct url_stat *us, char *flags) 532{ 533 char *ln, *s; 534 struct tm tm; 535 time_t t; 536 int e, cd; 537 538 us->size = -1; 539 us->atime = us->mtime = 0; 540 541 /* connect to server */ 542 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 543 return -1; 544 545 /* change directory */ 546 if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) { 547 *s = 0; 548 if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) { 549 *s = '/'; 550 goto ouch; 551 } 552 *s++ = '/'; 553 } else { 554 if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) 555 goto ouch; 556 } 557 558 /* s now points to file name */ 559 560 if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS) 561 goto ouch; 562 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 563 /* nothing */ ; 564 for (us->size = 0; *ln && isdigit(*ln); ln++) 565 us->size = us->size * 10 + *ln - '0'; 566 if (*ln && !isspace(*ln)) { 567 _ftp_seterr(999); 568 return -1; 569 } 570 DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); 571 572 if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) 573 goto ouch; 574 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 575 /* nothing */ ; 576 e = 999; 577 switch (strspn(ln, "0123456789")) { 578 case 14: 579 break; 580 case 15: 581 ln++; 582 ln[0] = '2'; 583 ln[1] = '0'; 584 break; 585 default: 586 goto ouch; 587 } 588 if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 589 &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 590 &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) 591 goto ouch; 592 tm.tm_mon--; 593 tm.tm_year -= 1900; 594 tm.tm_isdst = -1; 595 t = timegm(&tm); 596 if (t == (time_t)-1) 597 t = time(NULL); 598 us->mtime = t; 599 us->atime = t; 600 DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 601 "%02d:%02d:%02d\033[m]\n", 602 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 603 tm.tm_hour, tm.tm_min, tm.tm_sec)); 604 return 0; 605 606ouch: 607 if (e != -1) 608 _ftp_seterr(e); 609 return -1; 610} 611 612/* 613 * List a directory 614 */ 615extern void warnx(char *, ...); 616struct url_ent * 617fetchListFTP(struct url *url, char *flags) 618{ 619 warnx("fetchListFTP(): not implemented"); 620 return NULL; 621} 622