ftp.c revision 67259
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 67259 2000-10-17 19:01:18Z 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 <netinet/in.h> 61 62#include <ctype.h> 63#include <errno.h> 64#include <netdb.h> 65#include <stdarg.h> 66#include <stdio.h> 67#include <stdlib.h> 68#include <string.h> 69#include <time.h> 70#include <unistd.h> 71 72#include "fetch.h" 73#include "common.h" 74#include "ftperr.h" 75 76#define FTP_ANONYMOUS_USER "ftp" 77#define FTP_ANONYMOUS_PASSWORD "ftp" 78 79#define FTP_CONNECTION_ALREADY_OPEN 125 80#define FTP_OPEN_DATA_CONNECTION 150 81#define FTP_OK 200 82#define FTP_FILE_STATUS 213 83#define FTP_SERVICE_READY 220 84#define FTP_PASSIVE_MODE 227 85#define FTP_LPASSIVE_MODE 228 86#define FTP_EPASSIVE_MODE 229 87#define FTP_LOGGED_IN 230 88#define FTP_FILE_ACTION_OK 250 89#define FTP_NEED_PASSWORD 331 90#define FTP_NEED_ACCOUNT 332 91#define FTP_FILE_OK 350 92#define FTP_SYNTAX_ERROR 500 93#define FTP_PROTOCOL_ERROR 999 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/* translate IPv4 mapped IPv6 address to IPv4 address */ 109static void 110unmappedaddr(struct sockaddr_in6 *sin6) 111{ 112 struct sockaddr_in *sin4; 113 u_int32_t addr; 114 int port; 115 116 if (sin6->sin6_family != AF_INET6 || 117 !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 118 return; 119 sin4 = (struct sockaddr_in *)sin6; 120 addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 121 port = sin6->sin6_port; 122 memset(sin4, 0, sizeof(struct sockaddr_in)); 123 sin4->sin_addr.s_addr = addr; 124 sin4->sin_port = port; 125 sin4->sin_family = AF_INET; 126 sin4->sin_len = sizeof(struct sockaddr_in); 127} 128 129/* 130 * Get server response 131 */ 132static int 133_ftp_chkerr(int cd) 134{ 135 if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 136 _fetch_syserr(); 137 return -1; 138 } 139 if (isftpinfo(last_reply)) { 140 while (!isftpreply(last_reply)) { 141 if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 142 _fetch_syserr(); 143 return -1; 144 } 145 } 146 } 147 148 while (lr_length && isspace(last_reply[lr_length-1])) 149 lr_length--; 150 last_reply[lr_length] = 0; 151 152 if (!isftpreply(last_reply)) { 153 _ftp_seterr(FTP_PROTOCOL_ERROR); 154 return -1; 155 } 156 157 last_code = (last_reply[0] - '0') * 100 158 + (last_reply[1] - '0') * 10 159 + (last_reply[2] - '0'); 160 161 return last_code; 162} 163 164/* 165 * Send a command and check reply 166 */ 167static int 168_ftp_cmd(int cd, char *fmt, ...) 169{ 170 va_list ap; 171 size_t len; 172 char *msg; 173 int r; 174 175 va_start(ap, fmt); 176 len = vasprintf(&msg, fmt, ap); 177 va_end(ap); 178 179 if (msg == NULL) { 180 errno = ENOMEM; 181 _fetch_syserr(); 182 return -1; 183 } 184 185 r = _fetch_putln(cd, msg, len); 186 free(msg); 187 188 if (r == -1) { 189 _fetch_syserr(); 190 return -1; 191 } 192 193 return _ftp_chkerr(cd); 194} 195 196/* 197 * Return a pointer to the filename part of a path 198 */ 199static char * 200_ftp_filename(char *file) 201{ 202 char *s; 203 204 if ((s = strrchr(file, '/')) == NULL) 205 return file; 206 else 207 return s + 1; 208} 209 210/* 211 * Change working directory to the directory that contains the 212 * specified file. 213 */ 214static int 215_ftp_cwd(int cd, char *file) 216{ 217 char *s; 218 int e; 219 220 if ((s = strrchr(file, '/')) == NULL || s == file) { 221 e = _ftp_cmd(cd, "CWD /"); 222 } else { 223 e = _ftp_cmd(cd, "CWD %.*s", s - file, file); 224 } 225 if (e != FTP_FILE_ACTION_OK) { 226 _ftp_seterr(e); 227 return -1; 228 } 229 return 0; 230} 231 232/* 233 * Request and parse file stats 234 */ 235static int 236_ftp_stat(int cd, char *file, struct url_stat *us) 237{ 238 char *ln, *s; 239 struct tm tm; 240 time_t t; 241 int e; 242 243 us->size = -1; 244 us->atime = us->mtime = 0; 245 246 if ((s = strrchr(file, '/')) == NULL) 247 s = file; 248 else 249 ++s; 250 251 if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) { 252 _ftp_seterr(e); 253 return -1; 254 } 255 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 256 /* nothing */ ; 257 for (us->size = 0; *ln && isdigit(*ln); ln++) 258 us->size = us->size * 10 + *ln - '0'; 259 if (*ln && !isspace(*ln)) { 260 _ftp_seterr(FTP_PROTOCOL_ERROR); 261 return -1; 262 } 263 if (us->size == 0) 264 us->size = -1; 265 DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); 266 267 if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) { 268 _ftp_seterr(e); 269 return -1; 270 } 271 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 272 /* nothing */ ; 273 switch (strspn(ln, "0123456789")) { 274 case 14: 275 break; 276 case 15: 277 ln++; 278 ln[0] = '2'; 279 ln[1] = '0'; 280 break; 281 default: 282 _ftp_seterr(FTP_PROTOCOL_ERROR); 283 return -1; 284 } 285 if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 286 &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 287 &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { 288 _ftp_seterr(FTP_PROTOCOL_ERROR); 289 return -1; 290 } 291 tm.tm_mon--; 292 tm.tm_year -= 1900; 293 tm.tm_isdst = -1; 294 t = timegm(&tm); 295 if (t == (time_t)-1) 296 t = time(NULL); 297 us->mtime = t; 298 us->atime = t; 299 DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 300 "%02d:%02d:%02d\033[m]\n", 301 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 302 tm.tm_hour, tm.tm_min, tm.tm_sec)); 303 return 0; 304} 305 306/* 307 * Transfer file 308 */ 309static FILE * 310_ftp_transfer(int cd, char *oper, char *file, 311 char *mode, off_t offset, char *flags) 312{ 313 struct sockaddr_storage sin; 314 struct sockaddr_in6 *sin6; 315 struct sockaddr_in *sin4; 316 int pasv, high, verbose; 317 int e, sd = -1; 318 socklen_t l; 319 char *s; 320 FILE *df; 321 322 /* check flags */ 323 pasv = (flags && strchr(flags, 'p')); 324 high = (flags && strchr(flags, 'h')); 325 verbose = (flags && strchr(flags, 'v')); 326 327 /* passive mode */ 328 if (!pasv) 329 pasv = ((s = getenv("FTP_PASSIVE_MODE")) == NULL || 330 strncasecmp(s, "no", 2) != 0); 331 332 /* find our own address, bind, and listen */ 333 l = sizeof sin; 334 if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 335 goto sysouch; 336 if (sin.ss_family == AF_INET6) 337 unmappedaddr((struct sockaddr_in6 *)&sin); 338 339 /* open data socket */ 340 if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 341 _fetch_syserr(); 342 return NULL; 343 } 344 345 if (pasv) { 346 u_char addr[64]; 347 char *ln, *p; 348 int i; 349 int port; 350 351 /* send PASV command */ 352 if (verbose) 353 _fetch_info("setting passive mode"); 354 switch (sin.ss_family) { 355 case AF_INET: 356 if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 357 goto ouch; 358 break; 359 case AF_INET6: 360 if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 361 if (e == -1) 362 goto ouch; 363 if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 364 goto ouch; 365 } 366 break; 367 default: 368 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 369 goto ouch; 370 } 371 372 /* 373 * Find address and port number. The reply to the PASV command 374 * is IMHO the one and only weak point in the FTP protocol. 375 */ 376 ln = last_reply; 377 switch (e) { 378 case FTP_PASSIVE_MODE: 379 case FTP_LPASSIVE_MODE: 380 for (p = ln + 3; *p && !isdigit(*p); p++) 381 /* nothing */ ; 382 if (!*p) { 383 e = FTP_PROTOCOL_ERROR; 384 goto ouch; 385 } 386 l = (e == FTP_PASSIVE_MODE ? 6 : 21); 387 for (i = 0; *p && i < l; i++, p++) 388 addr[i] = strtol(p, &p, 10); 389 if (i < l) { 390 e = FTP_PROTOCOL_ERROR; 391 goto ouch; 392 } 393 break; 394 case FTP_EPASSIVE_MODE: 395 for (p = ln + 3; *p && *p != '('; p++) 396 /* nothing */ ; 397 if (!*p) { 398 e = FTP_PROTOCOL_ERROR; 399 goto ouch; 400 } 401 ++p; 402 if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 403 &port, &addr[3]) != 5 || 404 addr[0] != addr[1] || 405 addr[0] != addr[2] || addr[0] != addr[3]) { 406 e = FTP_PROTOCOL_ERROR; 407 goto ouch; 408 } 409 break; 410 } 411 412 /* seek to required offset */ 413 if (offset) 414 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 415 goto sysouch; 416 417 /* construct sockaddr for data socket */ 418 l = sizeof sin; 419 if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 420 goto sysouch; 421 if (sin.ss_family == AF_INET6) 422 unmappedaddr((struct sockaddr_in6 *)&sin); 423 switch (sin.ss_family) { 424 case AF_INET6: 425 sin6 = (struct sockaddr_in6 *)&sin; 426 if (e == FTP_EPASSIVE_MODE) 427 sin6->sin6_port = htons(port); 428 else { 429 bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 430 bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 431 } 432 break; 433 case AF_INET: 434 sin4 = (struct sockaddr_in *)&sin; 435 if (e == FTP_EPASSIVE_MODE) 436 sin4->sin_port = htons(port); 437 else { 438 bcopy(addr, (char *)&sin4->sin_addr, 4); 439 bcopy(addr + 4, (char *)&sin4->sin_port, 2); 440 } 441 break; 442 default: 443 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 444 break; 445 } 446 447 /* connect to data port */ 448 if (verbose) 449 _fetch_info("opening data connection"); 450 if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 451 goto sysouch; 452 453 /* make the server initiate the transfer */ 454 if (verbose) 455 _fetch_info("initiating transfer"); 456 e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 457 if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) 458 goto ouch; 459 460 } else { 461 u_int32_t a; 462 u_short p; 463 int arg, d; 464 char *ap; 465 char hname[INET6_ADDRSTRLEN]; 466 467 switch (sin.ss_family) { 468 case AF_INET6: 469 ((struct sockaddr_in6 *)&sin)->sin6_port = 0; 470#ifdef IPV6_PORTRANGE 471 arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT; 472 if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 473 (char *)&arg, sizeof(arg)) == -1) 474 goto sysouch; 475#endif 476 break; 477 case AF_INET: 478 ((struct sockaddr_in *)&sin)->sin_port = 0; 479 arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; 480 if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 481 (char *)&arg, sizeof arg) == -1) 482 goto sysouch; 483 break; 484 } 485 if (verbose) 486 _fetch_info("binding data socket"); 487 if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 488 goto sysouch; 489 if (listen(sd, 1) == -1) 490 goto sysouch; 491 492 /* find what port we're on and tell the server */ 493 if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 494 goto sysouch; 495 switch (sin.ss_family) { 496 case AF_INET: 497 sin4 = (struct sockaddr_in *)&sin; 498 a = ntohl(sin4->sin_addr.s_addr); 499 p = ntohs(sin4->sin_port); 500 e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 501 (a >> 24) & 0xff, (a >> 16) & 0xff, 502 (a >> 8) & 0xff, a & 0xff, 503 (p >> 8) & 0xff, p & 0xff); 504 break; 505 case AF_INET6: 506#define UC(b) (((int)b)&0xff) 507 e = -1; 508 sin6 = (struct sockaddr_in6 *)&sin; 509 if (getnameinfo((struct sockaddr *)&sin, sin.ss_len, 510 hname, sizeof(hname), 511 NULL, 0, NI_NUMERICHOST) == 0) { 512 e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 513 htons(sin6->sin6_port)); 514 if (e == -1) 515 goto ouch; 516 } 517 if (e != FTP_OK) { 518 ap = (char *)&sin6->sin6_addr; 519 e = _ftp_cmd(cd, 520 "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 521 6, 16, 522 UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 523 UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 524 UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 525 UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 526 2, 527 (ntohs(sin6->sin6_port) >> 8) & 0xff, 528 ntohs(sin6->sin6_port) & 0xff); 529 } 530 break; 531 default: 532 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 533 goto ouch; 534 } 535 if (e != FTP_OK) 536 goto ouch; 537 538 /* seek to required offset */ 539 if (offset) 540 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 541 goto sysouch; 542 543 /* make the server initiate the transfer */ 544 if (verbose) 545 _fetch_info("initiating transfer"); 546 e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 547 if (e != FTP_OPEN_DATA_CONNECTION) 548 goto ouch; 549 550 /* accept the incoming connection and go to town */ 551 if ((d = accept(sd, NULL, NULL)) == -1) 552 goto sysouch; 553 close(sd); 554 sd = d; 555 } 556 557 if ((df = fdopen(sd, mode)) == NULL) 558 goto sysouch; 559 return df; 560 561sysouch: 562 _fetch_syserr(); 563 if (sd >= 0) 564 close(sd); 565 return NULL; 566 567ouch: 568 if (e != -1) 569 _ftp_seterr(e); 570 if (sd >= 0) 571 close(sd); 572 return NULL; 573} 574 575/* 576 * Return default port 577 */ 578static int 579_ftp_default_port(void) 580{ 581 struct servent *se; 582 583 if ((se = getservbyname(SCHEME_FTP, "tcp")) != NULL) 584 return ntohs(se->s_port); 585 return FTP_DEFAULT_PORT; 586} 587 588/* 589 * Log on to FTP server 590 */ 591static int 592_ftp_connect(struct url *url, struct url *purl, char *flags) 593{ 594 int cd, e, direct, verbose; 595#ifdef INET6 596 int af = AF_UNSPEC; 597#else 598 int af = AF_INET; 599#endif 600 const char *logname; 601 char *user, *pwd; 602 char localhost[MAXHOSTNAMELEN]; 603 char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 604 605 direct = (flags && strchr(flags, 'd')); 606 verbose = (flags && strchr(flags, 'v')); 607 if ((flags && strchr(flags, '4'))) 608 af = AF_INET; 609 else if ((flags && strchr(flags, '6'))) 610 af = AF_INET6; 611 612 if (direct) 613 purl = NULL; 614 615 /* check for proxy */ 616 if (purl) { 617 /* XXX proxy authentication! */ 618 cd = _fetch_connect(purl->host, purl->port, af, verbose); 619 } else { 620 /* no proxy, go straight to target */ 621 cd = _fetch_connect(url->host, url->port, af, verbose); 622 purl = NULL; 623 } 624 625 /* check connection */ 626 if (cd == -1) { 627 _fetch_syserr(); 628 return NULL; 629 } 630 631 /* expect welcome message */ 632 if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 633 goto fouch; 634 635 /* XXX FTP_AUTH, and maybe .netrc */ 636 637 /* send user name and password */ 638 user = url->user; 639 if (!user || !*user) 640 user = FTP_ANONYMOUS_USER; 641 if (purl && url->port == FTP_DEFAULT_PORT) 642 e = _ftp_cmd(cd, "USER %s@%s", user, url->host); 643 else if (purl) 644 e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port); 645 else 646 e = _ftp_cmd(cd, "USER %s", user); 647 648 /* did the server request a password? */ 649 if (e == FTP_NEED_PASSWORD) { 650 pwd = url->pwd; 651 if (!pwd || !*pwd) 652 pwd = getenv("FTP_PASSWORD"); 653 if (!pwd || !*pwd) { 654 if ((logname = getlogin()) == 0) 655 logname = FTP_ANONYMOUS_PASSWORD; 656 gethostname(localhost, sizeof localhost); 657 snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost); 658 pwd = pbuf; 659 } 660 e = _ftp_cmd(cd, "PASS %s", pwd); 661 } 662 663 /* did the server request an account? */ 664 if (e == FTP_NEED_ACCOUNT) 665 goto fouch; 666 667 /* we should be done by now */ 668 if (e != FTP_LOGGED_IN) 669 goto fouch; 670 671 /* might as well select mode and type at once */ 672#ifdef FTP_FORCE_STREAM_MODE 673 if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 674 goto fouch; 675#endif 676 if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 677 goto fouch; 678 679 /* done */ 680 return cd; 681 682fouch: 683 if (e != -1) 684 _ftp_seterr(e); 685 close(cd); 686 return NULL; 687} 688 689/* 690 * Disconnect from server 691 */ 692static void 693_ftp_disconnect(int cd) 694{ 695 (void)_ftp_cmd(cd, "QUIT"); 696 close(cd); 697} 698 699/* 700 * Check if we're already connected 701 */ 702static int 703_ftp_isconnected(struct url *url) 704{ 705 return (cached_socket 706 && (strcmp(url->host, cached_host.host) == 0) 707 && (strcmp(url->user, cached_host.user) == 0) 708 && (strcmp(url->pwd, cached_host.pwd) == 0) 709 && (url->port == cached_host.port)); 710} 711 712/* 713 * Check the cache, reconnect if no luck 714 */ 715static int 716_ftp_cached_connect(struct url *url, struct url *purl, char *flags) 717{ 718 int e, cd; 719 720 cd = -1; 721 722 /* set default port */ 723 if (!url->port) 724 url->port = _ftp_default_port(); 725 726 /* try to use previously cached connection */ 727 if (_ftp_isconnected(url)) { 728 e = _ftp_cmd(cached_socket, "NOOP"); 729 if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 730 return cached_socket; 731 } 732 733 /* connect to server */ 734 if ((cd = _ftp_connect(url, purl, flags)) == -1) 735 return -1; 736 if (cached_socket) 737 _ftp_disconnect(cached_socket); 738 cached_socket = cd; 739 memcpy(&cached_host, url, sizeof *url); 740 return cd; 741} 742 743/* 744 * Check the proxy settings 745 */ 746static struct url * 747_ftp_get_proxy(void) 748{ 749 struct url *purl; 750 char *p; 751 752 if (((p = getenv("FTP_PROXY")) || (p = getenv("HTTP_PROXY"))) && 753 *p && (purl = fetchParseURL(p)) != NULL) { 754 if (!*purl->scheme) 755 strcpy(purl->scheme, SCHEME_FTP); 756 if (!purl->port) 757 purl->port = _ftp_default_port(); 758 if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 759 strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 760 return purl; 761 fetchFreeURL(purl); 762 } 763 return NULL; 764} 765 766/* 767 * Get and stat file 768 */ 769FILE * 770fetchXGetFTP(struct url *url, struct url_stat *us, char *flags) 771{ 772 struct url *purl; 773 int cd; 774 775 /* get the proxy URL, and check if we should use HTTP instead */ 776 if (!strchr(flags, 'd') && (purl = _ftp_get_proxy()) != NULL) { 777 if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 778 return _http_request(url, "GET", us, purl, flags); 779 } else { 780 purl = NULL; 781 } 782 783 /* connect to server */ 784 cd = _ftp_cached_connect(url, purl, flags); 785 if (purl) 786 fetchFreeURL(purl); 787 if (cd == NULL) 788 return NULL; 789 790 /* change directory */ 791 if (_ftp_cwd(cd, url->doc) == -1) 792 return NULL; 793 794 /* stat file */ 795 if (us && _ftp_stat(cd, url->doc, us) == -1 796 && fetchLastErrCode != FETCH_PROTO 797 && fetchLastErrCode != FETCH_UNAVAIL) 798 return NULL; 799 800 /* initiate the transfer */ 801 return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags); 802} 803 804/* 805 * Get file 806 */ 807FILE * 808fetchGetFTP(struct url *url, char *flags) 809{ 810 return fetchXGetFTP(url, NULL, flags); 811} 812 813/* 814 * Put file 815 */ 816FILE * 817fetchPutFTP(struct url *url, char *flags) 818{ 819 struct url *purl; 820 int cd; 821 822 /* get the proxy URL, and check if we should use HTTP instead */ 823 if (!strchr(flags, 'd') && (purl = _ftp_get_proxy()) != NULL) { 824 if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 825 /* XXX HTTP PUT is not implemented, so try without the proxy */ 826 purl = NULL; 827 } else { 828 purl = NULL; 829 } 830 831 /* connect to server */ 832 cd = _ftp_cached_connect(url, purl, flags); 833 if (purl) 834 fetchFreeURL(purl); 835 if (cd == NULL) 836 return NULL; 837 838 /* change directory */ 839 if (_ftp_cwd(cd, url->doc) == -1) 840 return NULL; 841 842 /* initiate the transfer */ 843 return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR", 844 url->doc, "w", url->offset, flags); 845} 846 847/* 848 * Get file stats 849 */ 850int 851fetchStatFTP(struct url *url, struct url_stat *us, char *flags) 852{ 853 struct url *purl; 854 int cd; 855 856 /* get the proxy URL, and check if we should use HTTP instead */ 857 if (!strchr(flags, 'd') && (purl = _ftp_get_proxy()) != NULL) { 858 if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) { 859 FILE *f; 860 861 if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL) 862 return -1; 863 fclose(f); 864 return 0; 865 } 866 } else { 867 purl = NULL; 868 } 869 870 /* connect to server */ 871 cd = _ftp_cached_connect(url, purl, flags); 872 if (purl) 873 fetchFreeURL(purl); 874 if (cd == NULL) 875 return NULL; 876 877 /* change directory */ 878 if (_ftp_cwd(cd, url->doc) == -1) 879 return -1; 880 881 /* stat file */ 882 return _ftp_stat(cd, url->doc, us); 883} 884 885/* 886 * List a directory 887 */ 888extern void warnx(char *, ...); 889struct url_ent * 890fetchListFTP(struct url *url, char *flags) 891{ 892 warnx("fetchListFTP(): not implemented"); 893 return NULL; 894} 895