ftp.c revision 75292
1299425Smm/*- 2299425Smm * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav 3299425Smm * All rights reserved. 4299425Smm * 5299425Smm * Redistribution and use in source and binary forms, with or without 6299425Smm * modification, are permitted provided that the following conditions 7299425Smm * are met: 8299425Smm * 1. Redistributions of source code must retain the above copyright 9299425Smm * notice, this list of conditions and the following disclaimer 10299425Smm * in this position and unchanged. 11299425Smm * 2. Redistributions in binary form must reproduce the above copyright 12299425Smm * notice, this list of conditions and the following disclaimer in the 13299425Smm * documentation and/or other materials provided with the distribution. 14299425Smm * 3. The name of the author may not be used to endorse or promote products 15299425Smm * derived from this software without specific prior written permission 16299425Smm * 17299425Smm * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18299425Smm * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19299425Smm * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20299425Smm * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21299425Smm * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22299425Smm * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23299425Smm * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24299425Smm * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25299425Smm * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26299425Smm * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27299425Smm * 28299425Smm * $FreeBSD: head/lib/libfetch/ftp.c 75292 2001-04-07 15:26:31Z des $ 29299425Smm */ 30299425Smm 31299425Smm/* 32299425Smm * Portions of this code were taken from or based on ftpio.c: 33299425Smm * 34299425Smm * ---------------------------------------------------------------------------- 35299425Smm * "THE BEER-WARE LICENSE" (Revision 42): 36299425Smm * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you 37299425Smm * can do whatever you want with this stuff. If we meet some day, and you think 38299425Smm * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 39299425Smm * ---------------------------------------------------------------------------- 40299425Smm * 41299425Smm * Major Changelog: 42299425Smm * 43299425Smm * Dag-Erling Co�dan Sm�rgrav 44299425Smm * 9 Jun 1998 45299425Smm * 46299425Smm * Incorporated into libfetch 47299425Smm * 48299425Smm * Jordan K. Hubbard 49299425Smm * 17 Jan 1996 50299425Smm * 51299425Smm * Turned inside out. Now returns xfers as new file ids, not as a special 52299425Smm * `state' of FTP_t 53299425Smm * 54299425Smm * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $ 55299425Smm * 56299425Smm */ 57299425Smm 58299425Smm#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 <fcntl.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 "anonymous" 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_TRANSFER_COMPLETE 226 85#define FTP_PASSIVE_MODE 227 86#define FTP_LPASSIVE_MODE 228 87#define FTP_EPASSIVE_MODE 229 88#define FTP_LOGGED_IN 230 89#define FTP_FILE_ACTION_OK 250 90#define FTP_NEED_PASSWORD 331 91#define FTP_NEED_ACCOUNT 332 92#define FTP_FILE_OK 350 93#define FTP_SYNTAX_ERROR 500 94#define FTP_PROTOCOL_ERROR 999 95 96static struct url cached_host; 97static int cached_socket; 98 99static char *last_reply; 100static size_t lr_size, lr_length; 101static int last_code; 102 103#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 104 && isdigit(foo[2]) \ 105 && (foo[3] == ' ' || foo[3] == '\0')) 106#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 107 && isdigit(foo[2]) && foo[3] == '-') 108 109/* translate IPv4 mapped IPv6 address to IPv4 address */ 110static void 111unmappedaddr(struct sockaddr_in6 *sin6) 112{ 113 struct sockaddr_in *sin4; 114 u_int32_t addr; 115 int port; 116 117 if (sin6->sin6_family != AF_INET6 || 118 !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 119 return; 120 sin4 = (struct sockaddr_in *)sin6; 121 addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 122 port = sin6->sin6_port; 123 memset(sin4, 0, sizeof(struct sockaddr_in)); 124 sin4->sin_addr.s_addr = addr; 125 sin4->sin_port = port; 126 sin4->sin_family = AF_INET; 127 sin4->sin_len = sizeof(struct sockaddr_in); 128} 129 130/* 131 * Get server response 132 */ 133static int 134_ftp_chkerr(int cd) 135{ 136 if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 137 _fetch_syserr(); 138 return -1; 139 } 140 if (isftpinfo(last_reply)) { 141 while (lr_length && !isftpreply(last_reply)) { 142 if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 143 _fetch_syserr(); 144 return -1; 145 } 146 } 147 } 148 149 while (lr_length && isspace(last_reply[lr_length-1])) 150 lr_length--; 151 last_reply[lr_length] = 0; 152 153 if (!isftpreply(last_reply)) { 154 _ftp_seterr(FTP_PROTOCOL_ERROR); 155 return -1; 156 } 157 158 last_code = (last_reply[0] - '0') * 100 159 + (last_reply[1] - '0') * 10 160 + (last_reply[2] - '0'); 161 162 return last_code; 163} 164 165/* 166 * Send a command and check reply 167 */ 168static int 169_ftp_cmd(int cd, char *fmt, ...) 170{ 171 va_list ap; 172 size_t len; 173 char *msg; 174 int r; 175 176 va_start(ap, fmt); 177 len = vasprintf(&msg, fmt, ap); 178 va_end(ap); 179 180 if (msg == NULL) { 181 errno = ENOMEM; 182 _fetch_syserr(); 183 return -1; 184 } 185 186 r = _fetch_putln(cd, msg, len); 187 free(msg); 188 189 if (r == -1) { 190 _fetch_syserr(); 191 return -1; 192 } 193 194 return _ftp_chkerr(cd); 195} 196 197/* 198 * Return a pointer to the filename part of a path 199 */ 200static char * 201_ftp_filename(char *file) 202{ 203 char *s; 204 205 if ((s = strrchr(file, '/')) == NULL) 206 return file; 207 else 208 return s + 1; 209} 210 211/* 212 * Change working directory to the directory that contains the 213 * specified file. 214 */ 215static int 216_ftp_cwd(int cd, char *file) 217{ 218 char *s; 219 int e; 220 221 if ((s = strrchr(file, '/')) == NULL || s == file) { 222 e = _ftp_cmd(cd, "CWD /"); 223 } else { 224 e = _ftp_cmd(cd, "CWD %.*s", s - file, file); 225 } 226 if (e != FTP_FILE_ACTION_OK) { 227 _ftp_seterr(e); 228 return -1; 229 } 230 return 0; 231} 232 233/* 234 * Request and parse file stats 235 */ 236static int 237_ftp_stat(int cd, char *file, struct url_stat *us) 238{ 239 char *ln, *s; 240 struct tm tm; 241 time_t t; 242 int e; 243 244 us->size = -1; 245 us->atime = us->mtime = 0; 246 247 if ((s = strrchr(file, '/')) == NULL) 248 s = file; 249 else 250 ++s; 251 252 if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) { 253 _ftp_seterr(e); 254 return -1; 255 } 256 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 257 /* nothing */ ; 258 for (us->size = 0; *ln && isdigit(*ln); ln++) 259 us->size = us->size * 10 + *ln - '0'; 260 if (*ln && !isspace(*ln)) { 261 _ftp_seterr(FTP_PROTOCOL_ERROR); 262 us->size = -1; 263 return -1; 264 } 265 if (us->size == 0) 266 us->size = -1; 267 DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); 268 269 if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) { 270 _ftp_seterr(e); 271 return -1; 272 } 273 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 274 /* nothing */ ; 275 switch (strspn(ln, "0123456789")) { 276 case 14: 277 break; 278 case 15: 279 ln++; 280 ln[0] = '2'; 281 ln[1] = '0'; 282 break; 283 default: 284 _ftp_seterr(FTP_PROTOCOL_ERROR); 285 return -1; 286 } 287 if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 288 &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 289 &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { 290 _ftp_seterr(FTP_PROTOCOL_ERROR); 291 return -1; 292 } 293 tm.tm_mon--; 294 tm.tm_year -= 1900; 295 tm.tm_isdst = -1; 296 t = timegm(&tm); 297 if (t == (time_t)-1) 298 t = time(NULL); 299 us->mtime = t; 300 us->atime = t; 301 DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 302 "%02d:%02d:%02d\033[m]\n", 303 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 304 tm.tm_hour, tm.tm_min, tm.tm_sec)); 305 return 0; 306} 307 308/* 309 * I/O functions for FTP 310 */ 311struct ftpio { 312 int csd; /* Control socket descriptor */ 313 int dsd; /* Data socket descriptor */ 314 int dir; /* Direction */ 315 int eof; /* EOF reached */ 316 int err; /* Error code */ 317}; 318 319static int _ftp_readfn(void *, char *, int); 320static int _ftp_writefn(void *, const char *, int); 321static fpos_t _ftp_seekfn(void *, fpos_t, int); 322static int _ftp_closefn(void *); 323 324static int 325_ftp_readfn(void *v, char *buf, int len) 326{ 327 struct ftpio *io; 328 int r; 329 330 io = (struct ftpio *)v; 331 if (io == NULL) { 332 errno = EBADF; 333 return -1; 334 } 335 if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) { 336 errno = EBADF; 337 return -1; 338 } 339 if (io->err) { 340 errno = io->err; 341 return -1; 342 } 343 if (io->eof) 344 return 0; 345 r = read(io->dsd, buf, len); 346 if (r > 0) 347 return r; 348 if (r == 0) { 349 io->eof = 1; 350 return _ftp_closefn(v); 351 } 352 if (errno != EINTR) 353 io->err = errno; 354 return -1; 355} 356 357static int 358_ftp_writefn(void *v, const char *buf, int len) 359{ 360 struct ftpio *io; 361 int w; 362 363 io = (struct ftpio *)v; 364 if (io == NULL) { 365 errno = EBADF; 366 return -1; 367 } 368 if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) { 369 errno = EBADF; 370 return -1; 371 } 372 if (io->err) { 373 errno = io->err; 374 return -1; 375 } 376 w = write(io->dsd, buf, len); 377 if (w >= 0) 378 return w; 379 if (errno != EINTR) 380 io->err = errno; 381 return -1; 382} 383 384static fpos_t 385_ftp_seekfn(void *v, fpos_t pos, int whence) 386{ 387 struct ftpio *io; 388 389 io = (struct ftpio *)v; 390 if (io == NULL) { 391 errno = EBADF; 392 return -1; 393 } 394 errno = ESPIPE; 395 return -1; 396} 397 398static int 399_ftp_closefn(void *v) 400{ 401 struct ftpio *io; 402 int r; 403 404 io = (struct ftpio *)v; 405 if (io == NULL) { 406 errno = EBADF; 407 return -1; 408 } 409 if (io->dir == -1) 410 return 0; 411 if (io->csd == -1 || io->dsd == -1) { 412 errno = EBADF; 413 return -1; 414 } 415 close(io->dsd); 416 io->dir = -1; 417 io->dsd = -1; 418 DEBUG(fprintf(stderr, "Waiting for final status\n")); 419 if ((r = _ftp_chkerr(io->csd)) != FTP_TRANSFER_COMPLETE) 420 io->err = r; 421 else 422 io->err = 0; 423 close(io->csd); 424 io->csd = -1; 425 return io->err ? -1 : 0; 426} 427 428static FILE * 429_ftp_setup(int csd, int dsd, int mode) 430{ 431 struct ftpio *io; 432 FILE *f; 433 434 if ((io = malloc(sizeof *io)) == NULL) 435 return NULL; 436 io->csd = dup(csd); 437 io->dsd = dsd; 438 io->dir = mode; 439 io->eof = io->err = 0; 440 f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn); 441 if (f == NULL) 442 free(io); 443 return f; 444} 445 446/* 447 * Transfer file 448 */ 449static FILE * 450_ftp_transfer(int cd, char *oper, char *file, 451 int mode, off_t offset, char *flags) 452{ 453 struct sockaddr_storage sin; 454 struct sockaddr_in6 *sin6; 455 struct sockaddr_in *sin4; 456 int low, pasv, verbose; 457 int e, sd = -1; 458 socklen_t l; 459 char *s; 460 FILE *df; 461 462 /* check flags */ 463 low = CHECK_FLAG('l'); 464 pasv = CHECK_FLAG('p'); 465 verbose = CHECK_FLAG('v'); 466 467 /* passive mode */ 468 if (!pasv) 469 pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL && 470 strncasecmp(s, "no", 2) != 0); 471 472 /* find our own address, bind, and listen */ 473 l = sizeof sin; 474 if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 475 goto sysouch; 476 if (sin.ss_family == AF_INET6) 477 unmappedaddr((struct sockaddr_in6 *)&sin); 478 479 /* open data socket */ 480 if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 481 _fetch_syserr(); 482 return NULL; 483 } 484 485 if (pasv) { 486 u_char addr[64]; 487 char *ln, *p; 488 int i; 489 int port; 490 491 /* send PASV command */ 492 if (verbose) 493 _fetch_info("setting passive mode"); 494 switch (sin.ss_family) { 495 case AF_INET: 496 if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 497 goto ouch; 498 break; 499 case AF_INET6: 500 if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 501 if (e == -1) 502 goto ouch; 503 if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 504 goto ouch; 505 } 506 break; 507 default: 508 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 509 goto ouch; 510 } 511 512 /* 513 * Find address and port number. The reply to the PASV command 514 * is IMHO the one and only weak point in the FTP protocol. 515 */ 516 ln = last_reply; 517 switch (e) { 518 case FTP_PASSIVE_MODE: 519 case FTP_LPASSIVE_MODE: 520 for (p = ln + 3; *p && !isdigit(*p); p++) 521 /* nothing */ ; 522 if (!*p) { 523 e = FTP_PROTOCOL_ERROR; 524 goto ouch; 525 } 526 l = (e == FTP_PASSIVE_MODE ? 6 : 21); 527 for (i = 0; *p && i < l; i++, p++) 528 addr[i] = strtol(p, &p, 10); 529 if (i < l) { 530 e = FTP_PROTOCOL_ERROR; 531 goto ouch; 532 } 533 break; 534 case FTP_EPASSIVE_MODE: 535 for (p = ln + 3; *p && *p != '('; p++) 536 /* nothing */ ; 537 if (!*p) { 538 e = FTP_PROTOCOL_ERROR; 539 goto ouch; 540 } 541 ++p; 542 if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 543 &port, &addr[3]) != 5 || 544 addr[0] != addr[1] || 545 addr[0] != addr[2] || addr[0] != addr[3]) { 546 e = FTP_PROTOCOL_ERROR; 547 goto ouch; 548 } 549 break; 550 } 551 552 /* seek to required offset */ 553 if (offset) 554 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 555 goto sysouch; 556 557 /* construct sockaddr for data socket */ 558 l = sizeof sin; 559 if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 560 goto sysouch; 561 if (sin.ss_family == AF_INET6) 562 unmappedaddr((struct sockaddr_in6 *)&sin); 563 switch (sin.ss_family) { 564 case AF_INET6: 565 sin6 = (struct sockaddr_in6 *)&sin; 566 if (e == FTP_EPASSIVE_MODE) 567 sin6->sin6_port = htons(port); 568 else { 569 bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 570 bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 571 } 572 break; 573 case AF_INET: 574 sin4 = (struct sockaddr_in *)&sin; 575 if (e == FTP_EPASSIVE_MODE) 576 sin4->sin_port = htons(port); 577 else { 578 bcopy(addr, (char *)&sin4->sin_addr, 4); 579 bcopy(addr + 4, (char *)&sin4->sin_port, 2); 580 } 581 break; 582 default: 583 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 584 break; 585 } 586 587 /* connect to data port */ 588 if (verbose) 589 _fetch_info("opening data connection"); 590 if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 591 goto sysouch; 592 593 /* make the server initiate the transfer */ 594 if (verbose) 595 _fetch_info("initiating transfer"); 596 e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 597 if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) 598 goto ouch; 599 600 } else { 601 u_int32_t a; 602 u_short p; 603 int arg, d; 604 char *ap; 605 char hname[INET6_ADDRSTRLEN]; 606 607 switch (sin.ss_family) { 608 case AF_INET6: 609 ((struct sockaddr_in6 *)&sin)->sin6_port = 0; 610#ifdef IPV6_PORTRANGE 611 arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH; 612 if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 613 (char *)&arg, sizeof(arg)) == -1) 614 goto sysouch; 615#endif 616 break; 617 case AF_INET: 618 ((struct sockaddr_in *)&sin)->sin_port = 0; 619 arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH; 620 if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 621 (char *)&arg, sizeof arg) == -1) 622 goto sysouch; 623 break; 624 } 625 if (verbose) 626 _fetch_info("binding data socket"); 627 if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 628 goto sysouch; 629 if (listen(sd, 1) == -1) 630 goto sysouch; 631 632 /* find what port we're on and tell the server */ 633 if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 634 goto sysouch; 635 switch (sin.ss_family) { 636 case AF_INET: 637 sin4 = (struct sockaddr_in *)&sin; 638 a = ntohl(sin4->sin_addr.s_addr); 639 p = ntohs(sin4->sin_port); 640 e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 641 (a >> 24) & 0xff, (a >> 16) & 0xff, 642 (a >> 8) & 0xff, a & 0xff, 643 (p >> 8) & 0xff, p & 0xff); 644 break; 645 case AF_INET6: 646#define UC(b) (((int)b)&0xff) 647 e = -1; 648 sin6 = (struct sockaddr_in6 *)&sin; 649 if (getnameinfo((struct sockaddr *)&sin, sin.ss_len, 650 hname, sizeof(hname), 651 NULL, 0, NI_NUMERICHOST) == 0) { 652 e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 653 htons(sin6->sin6_port)); 654 if (e == -1) 655 goto ouch; 656 } 657 if (e != FTP_OK) { 658 ap = (char *)&sin6->sin6_addr; 659 e = _ftp_cmd(cd, 660 "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 661 6, 16, 662 UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 663 UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 664 UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 665 UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 666 2, 667 (ntohs(sin6->sin6_port) >> 8) & 0xff, 668 ntohs(sin6->sin6_port) & 0xff); 669 } 670 break; 671 default: 672 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 673 goto ouch; 674 } 675 if (e != FTP_OK) 676 goto ouch; 677 678 /* seek to required offset */ 679 if (offset) 680 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 681 goto sysouch; 682 683 /* make the server initiate the transfer */ 684 if (verbose) 685 _fetch_info("initiating transfer"); 686 e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 687 if (e != FTP_OPEN_DATA_CONNECTION) 688 goto ouch; 689 690 /* accept the incoming connection and go to town */ 691 if ((d = accept(sd, NULL, NULL)) == -1) 692 goto sysouch; 693 close(sd); 694 sd = d; 695 } 696 697 if ((df = _ftp_setup(cd, sd, mode)) == NULL) 698 goto sysouch; 699 return df; 700 701sysouch: 702 _fetch_syserr(); 703 if (sd >= 0) 704 close(sd); 705 return NULL; 706 707ouch: 708 if (e != -1) 709 _ftp_seterr(e); 710 if (sd >= 0) 711 close(sd); 712 return NULL; 713} 714 715/* 716 * Log on to FTP server 717 */ 718static int 719_ftp_connect(struct url *url, struct url *purl, char *flags) 720{ 721 int cd, e, direct, verbose; 722#ifdef INET6 723 int af = AF_UNSPEC; 724#else 725 int af = AF_INET; 726#endif 727 const char *logname; 728 char *user, *pwd; 729 char localhost[MAXHOSTNAMELEN]; 730 char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 731 732 direct = CHECK_FLAG('d'); 733 verbose = CHECK_FLAG('v'); 734 if (CHECK_FLAG('4')) 735 af = AF_INET; 736 else if (CHECK_FLAG('6')) 737 af = AF_INET6; 738 739 if (direct) 740 purl = NULL; 741 742 /* check for proxy */ 743 if (purl) { 744 /* XXX proxy authentication! */ 745 cd = _fetch_connect(purl->host, purl->port, af, verbose); 746 } else { 747 /* no proxy, go straight to target */ 748 cd = _fetch_connect(url->host, url->port, af, verbose); 749 purl = NULL; 750 } 751 752 /* check connection */ 753 if (cd == -1) { 754 _fetch_syserr(); 755 return NULL; 756 } 757 758 /* expect welcome message */ 759 if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 760 goto fouch; 761 762 /* XXX FTP_AUTH, and maybe .netrc */ 763 764 /* send user name and password */ 765 user = url->user; 766 if (!user || !*user) 767 user = getenv("FTP_LOGIN"); 768 if (!user || !*user) 769 user = FTP_ANONYMOUS_USER; 770 if (purl && url->port == _fetch_default_port(url->scheme)) 771 e = _ftp_cmd(cd, "USER %s@%s", user, url->host); 772 else if (purl) 773 e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port); 774 else 775 e = _ftp_cmd(cd, "USER %s", user); 776 777 /* did the server request a password? */ 778 if (e == FTP_NEED_PASSWORD) { 779 pwd = url->pwd; 780 if (!pwd || !*pwd) 781 pwd = getenv("FTP_PASSWORD"); 782 if (!pwd || !*pwd) { 783 if ((logname = getlogin()) == 0) 784 logname = FTP_ANONYMOUS_USER; 785 gethostname(localhost, sizeof localhost); 786 snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost); 787 pwd = pbuf; 788 } 789 e = _ftp_cmd(cd, "PASS %s", pwd); 790 } 791 792 /* did the server request an account? */ 793 if (e == FTP_NEED_ACCOUNT) 794 goto fouch; 795 796 /* we should be done by now */ 797 if (e != FTP_LOGGED_IN) 798 goto fouch; 799 800 /* might as well select mode and type at once */ 801#ifdef FTP_FORCE_STREAM_MODE 802 if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 803 goto fouch; 804#endif 805 if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 806 goto fouch; 807 808 /* done */ 809 return cd; 810 811fouch: 812 if (e != -1) 813 _ftp_seterr(e); 814 close(cd); 815 return NULL; 816} 817 818/* 819 * Disconnect from server 820 */ 821static void 822_ftp_disconnect(int cd) 823{ 824 (void)_ftp_cmd(cd, "QUIT"); 825 close(cd); 826} 827 828/* 829 * Check if we're already connected 830 */ 831static int 832_ftp_isconnected(struct url *url) 833{ 834 return (cached_socket 835 && (strcmp(url->host, cached_host.host) == 0) 836 && (strcmp(url->user, cached_host.user) == 0) 837 && (strcmp(url->pwd, cached_host.pwd) == 0) 838 && (url->port == cached_host.port)); 839} 840 841/* 842 * Check the cache, reconnect if no luck 843 */ 844static int 845_ftp_cached_connect(struct url *url, struct url *purl, char *flags) 846{ 847 int e, cd; 848 849 cd = -1; 850 851 /* set default port */ 852 if (!url->port) 853 url->port = _fetch_default_port(url->scheme); 854 855 /* try to use previously cached connection */ 856 if (_ftp_isconnected(url)) { 857 e = _ftp_cmd(cached_socket, "NOOP"); 858 if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 859 return cached_socket; 860 } 861 862 /* connect to server */ 863 if ((cd = _ftp_connect(url, purl, flags)) == -1) 864 return -1; 865 if (cached_socket) 866 _ftp_disconnect(cached_socket); 867 cached_socket = cd; 868 memcpy(&cached_host, url, sizeof *url); 869 return cd; 870} 871 872/* 873 * Check the proxy settings 874 */ 875static struct url * 876_ftp_get_proxy(void) 877{ 878 struct url *purl; 879 char *p; 880 881 if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) || 882 (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) && 883 *p && (purl = fetchParseURL(p)) != NULL) { 884 if (!*purl->scheme) { 885 if (getenv("FTP_PROXY") || getenv("ftp_proxy")) 886 strcpy(purl->scheme, SCHEME_FTP); 887 else 888 strcpy(purl->scheme, SCHEME_HTTP); 889 } 890 if (!purl->port) 891 purl->port = _fetch_default_proxy_port(purl->scheme); 892 if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 893 strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 894 return purl; 895 fetchFreeURL(purl); 896 } 897 return NULL; 898} 899 900/* 901 * Get and stat file 902 */ 903FILE * 904fetchXGetFTP(struct url *url, struct url_stat *us, char *flags) 905{ 906 struct url *purl; 907 int cd; 908 909 /* get the proxy URL, and check if we should use HTTP instead */ 910 if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 911 if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 912 return _http_request(url, "GET", us, purl, flags); 913 } else { 914 purl = NULL; 915 } 916 917 /* connect to server */ 918 cd = _ftp_cached_connect(url, purl, flags); 919 if (purl) 920 fetchFreeURL(purl); 921 if (cd == NULL) 922 return NULL; 923 924 /* change directory */ 925 if (_ftp_cwd(cd, url->doc) == -1) 926 return NULL; 927 928 /* stat file */ 929 if (us && _ftp_stat(cd, url->doc, us) == -1 930 && fetchLastErrCode != FETCH_PROTO 931 && fetchLastErrCode != FETCH_UNAVAIL) 932 return NULL; 933 934 /* initiate the transfer */ 935 return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags); 936} 937 938/* 939 * Get file 940 */ 941FILE * 942fetchGetFTP(struct url *url, char *flags) 943{ 944 return fetchXGetFTP(url, NULL, flags); 945} 946 947/* 948 * Put file 949 */ 950FILE * 951fetchPutFTP(struct url *url, char *flags) 952{ 953 struct url *purl; 954 int cd; 955 956 /* get the proxy URL, and check if we should use HTTP instead */ 957 if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 958 if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 959 /* XXX HTTP PUT is not implemented, so try without the proxy */ 960 purl = NULL; 961 } else { 962 purl = NULL; 963 } 964 965 /* connect to server */ 966 cd = _ftp_cached_connect(url, purl, flags); 967 if (purl) 968 fetchFreeURL(purl); 969 if (cd == NULL) 970 return NULL; 971 972 /* change directory */ 973 if (_ftp_cwd(cd, url->doc) == -1) 974 return NULL; 975 976 /* initiate the transfer */ 977 return _ftp_transfer(cd, CHECK_FLAG('a') ? "APPE" : "STOR", 978 url->doc, O_WRONLY, url->offset, flags); 979} 980 981/* 982 * Get file stats 983 */ 984int 985fetchStatFTP(struct url *url, struct url_stat *us, char *flags) 986{ 987 struct url *purl; 988 int cd; 989 990 /* get the proxy URL, and check if we should use HTTP instead */ 991 if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 992 if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) { 993 FILE *f; 994 995 if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL) 996 return -1; 997 fclose(f); 998 return 0; 999 } 1000 } else { 1001 purl = NULL; 1002 } 1003 1004 /* connect to server */ 1005 cd = _ftp_cached_connect(url, purl, flags); 1006 if (purl) 1007 fetchFreeURL(purl); 1008 if (cd == NULL) 1009 return NULL; 1010 1011 /* change directory */ 1012 if (_ftp_cwd(cd, url->doc) == -1) 1013 return -1; 1014 1015 /* stat file */ 1016 return _ftp_stat(cd, url->doc, us); 1017} 1018 1019/* 1020 * List a directory 1021 */ 1022extern void warnx(char *, ...); 1023struct url_ent * 1024fetchListFTP(struct url *url, char *flags) 1025{ 1026 warnx("fetchListFTP(): not implemented"); 1027 return NULL; 1028} 1029