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