ftp.c revision 67890
1272343Sngie/*- 2272343Sngie * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav 3272343Sngie * All rights reserved. 4272343Sngie * 5272343Sngie * Redistribution and use in source and binary forms, with or without 6272343Sngie * modification, are permitted provided that the following conditions 7272343Sngie * are met: 8272343Sngie * 1. Redistributions of source code must retain the above copyright 9272343Sngie * notice, this list of conditions and the following disclaimer 10272343Sngie * in this position and unchanged. 11272343Sngie * 2. Redistributions in binary form must reproduce the above copyright 12272343Sngie * notice, this list of conditions and the following disclaimer in the 13272343Sngie * documentation and/or other materials provided with the distribution. 14272343Sngie * 3. The name of the author may not be used to endorse or promote products 15272343Sngie * derived from this software without specific prior written permission 16272343Sngie * 17272343Sngie * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18272343Sngie * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19272343Sngie * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20272343Sngie * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21272343Sngie * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22272343Sngie * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23272343Sngie * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24272343Sngie * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25272343Sngie * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26272343Sngie * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27272343Sngie * 28272343Sngie * $FreeBSD: head/lib/libfetch/ftp.c 67890 2000-10-29 15:52:05Z des $ 29272343Sngie */ 30272343Sngie 31272343Sngie/* 32272343Sngie * Portions of this code were taken from or based on ftpio.c: 33272343Sngie * 34272343Sngie * ---------------------------------------------------------------------------- 35272343Sngie * "THE BEER-WARE LICENSE" (Revision 42): 36272343Sngie * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you 37272343Sngie * can do whatever you want with this stuff. If we meet some day, and you think 38272343Sngie * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 39272343Sngie * ---------------------------------------------------------------------------- 40272343Sngie * 41272343Sngie * Major Changelog: 42272343Sngie * 43272343Sngie * Dag-Erling Co�dan Sm�rgrav 44272343Sngie * 9 Jun 1998 45272343Sngie * 46272343Sngie * Incorporated into libfetch 47272343Sngie * 48272343Sngie * Jordan K. Hubbard 49272343Sngie * 17 Jan 1996 50272343Sngie * 51272343Sngie * Turned inside out. Now returns xfers as new file ids, not as a special 52272343Sngie * `state' of FTP_t 53272343Sngie * 54272343Sngie * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $ 55272343Sngie * 56272343Sngie */ 57272343Sngie 58272343Sngie#include <sys/param.h> 59272343Sngie#include <sys/socket.h> 60272343Sngie#include <netinet/in.h> 61272343Sngie 62272343Sngie#include <ctype.h> 63272343Sngie#include <errno.h> 64272343Sngie#include <fcntl.h> 65272343Sngie#include <netdb.h> 66272343Sngie#include <stdarg.h> 67272343Sngie#include <stdio.h> 68272343Sngie#include <stdlib.h> 69272343Sngie#include <string.h> 70272343Sngie#include <time.h> 71272343Sngie#include <unistd.h> 72272343Sngie 73272343Sngie#include "fetch.h" 74272343Sngie#include "common.h" 75272343Sngie#include "ftperr.h" 76272343Sngie 77272343Sngie#define FTP_ANONYMOUS_USER "ftp" 78272343Sngie#define FTP_ANONYMOUS_PASSWORD "ftp" 79272343Sngie 80272343Sngie#define FTP_CONNECTION_ALREADY_OPEN 125 81272343Sngie#define FTP_OPEN_DATA_CONNECTION 150 82272343Sngie#define FTP_OK 200 83272343Sngie#define FTP_FILE_STATUS 213 84272343Sngie#define FTP_SERVICE_READY 220 85272343Sngie#define FTP_TRANSFER_COMPLETE 226 86272343Sngie#define FTP_PASSIVE_MODE 227 87272343Sngie#define FTP_LPASSIVE_MODE 228 88272343Sngie#define FTP_EPASSIVE_MODE 229 89272343Sngie#define FTP_LOGGED_IN 230 90272343Sngie#define FTP_FILE_ACTION_OK 250 91272343Sngie#define FTP_NEED_PASSWORD 331 92272343Sngie#define FTP_NEED_ACCOUNT 332 93272343Sngie#define FTP_FILE_OK 350 94272343Sngie#define FTP_SYNTAX_ERROR 500 95272343Sngie#define FTP_PROTOCOL_ERROR 999 96272343Sngie 97272343Sngiestatic struct url cached_host; 98272343Sngiestatic int cached_socket; 99272343Sngie 100272343Sngiestatic char *last_reply; 101272343Sngiestatic size_t lr_size, lr_length; 102272343Sngiestatic int last_code; 103272343Sngie 104272343Sngie#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 105272343Sngie && isdigit(foo[2]) \ 106272343Sngie && (foo[3] == ' ' || foo[3] == '\0')) 107272343Sngie#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 108272343Sngie && isdigit(foo[2]) && foo[3] == '-') 109272343Sngie 110272343Sngie/* translate IPv4 mapped IPv6 address to IPv4 address */ 111272343Sngiestatic void 112272343Sngieunmappedaddr(struct sockaddr_in6 *sin6) 113272343Sngie{ 114272343Sngie struct sockaddr_in *sin4; 115272343Sngie u_int32_t addr; 116272343Sngie int port; 117272343Sngie 118272343Sngie if (sin6->sin6_family != AF_INET6 || 119272343Sngie !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 120272343Sngie return; 121272343Sngie sin4 = (struct sockaddr_in *)sin6; 122272343Sngie addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 123272343Sngie port = sin6->sin6_port; 124272343Sngie memset(sin4, 0, sizeof(struct sockaddr_in)); 125272343Sngie sin4->sin_addr.s_addr = addr; 126272343Sngie sin4->sin_port = port; 127272343Sngie sin4->sin_family = AF_INET; 128272343Sngie sin4->sin_len = sizeof(struct sockaddr_in); 129272343Sngie} 130272343Sngie 131272343Sngie/* 132272343Sngie * Get server response 133272343Sngie */ 134272343Sngiestatic int 135272343Sngie_ftp_chkerr(int cd) 136272343Sngie{ 137272343Sngie if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 138272343Sngie _fetch_syserr(); 139272343Sngie return -1; 140272343Sngie } 141272343Sngie if (isftpinfo(last_reply)) { 142272343Sngie while (!isftpreply(last_reply)) { 143272343Sngie if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 144272343Sngie _fetch_syserr(); 145272343Sngie return -1; 146272343Sngie } 147272343Sngie } 148272343Sngie } 149272343Sngie 150272343Sngie while (lr_length && isspace(last_reply[lr_length-1])) 151272343Sngie lr_length--; 152272343Sngie last_reply[lr_length] = 0; 153272343Sngie 154272343Sngie if (!isftpreply(last_reply)) { 155272343Sngie _ftp_seterr(FTP_PROTOCOL_ERROR); 156272343Sngie return -1; 157272343Sngie } 158272343Sngie 159272343Sngie last_code = (last_reply[0] - '0') * 100 160272343Sngie + (last_reply[1] - '0') * 10 161272343Sngie + (last_reply[2] - '0'); 162272343Sngie 163272343Sngie return last_code; 164272343Sngie} 165272343Sngie 166272343Sngie/* 167272343Sngie * Send a command and check reply 168272343Sngie */ 169272343Sngiestatic int 170272343Sngie_ftp_cmd(int cd, char *fmt, ...) 171272343Sngie{ 172272343Sngie va_list ap; 173272343Sngie size_t len; 174272343Sngie char *msg; 175272343Sngie int r; 176272343Sngie 177272343Sngie va_start(ap, fmt); 178272343Sngie len = vasprintf(&msg, fmt, ap); 179272343Sngie va_end(ap); 180272343Sngie 181272343Sngie if (msg == NULL) { 182272343Sngie errno = ENOMEM; 183272343Sngie _fetch_syserr(); 184272343Sngie return -1; 185272343Sngie } 186272343Sngie 187272343Sngie r = _fetch_putln(cd, msg, len); 188272343Sngie free(msg); 189272343Sngie 190272343Sngie if (r == -1) { 191272343Sngie _fetch_syserr(); 192272343Sngie return -1; 193272343Sngie } 194272343Sngie 195272343Sngie return _ftp_chkerr(cd); 196272343Sngie} 197272343Sngie 198272343Sngie/* 199272343Sngie * Return a pointer to the filename part of a path 200272343Sngie */ 201272343Sngiestatic char * 202272343Sngie_ftp_filename(char *file) 203272343Sngie{ 204272343Sngie char *s; 205272343Sngie 206272343Sngie if ((s = strrchr(file, '/')) == NULL) 207272343Sngie return file; 208272343Sngie else 209272343Sngie return s + 1; 210272343Sngie} 211272343Sngie 212272343Sngie/* 213272343Sngie * Change working directory to the directory that contains the 214272343Sngie * specified file. 215272343Sngie */ 216272343Sngiestatic int 217272343Sngie_ftp_cwd(int cd, char *file) 218272343Sngie{ 219272343Sngie char *s; 220272343Sngie int e; 221272343Sngie 222272343Sngie if ((s = strrchr(file, '/')) == NULL || s == file) { 223272343Sngie e = _ftp_cmd(cd, "CWD /"); 224272343Sngie } else { 225272343Sngie e = _ftp_cmd(cd, "CWD %.*s", s - file, file); 226272343Sngie } 227272343Sngie if (e != FTP_FILE_ACTION_OK) { 228272343Sngie _ftp_seterr(e); 229272343Sngie return -1; 230272343Sngie } 231272343Sngie return 0; 232272343Sngie} 233272343Sngie 234272343Sngie/* 235272343Sngie * Request and parse file stats 236272343Sngie */ 237272343Sngiestatic int 238272343Sngie_ftp_stat(int cd, char *file, struct url_stat *us) 239272343Sngie{ 240272343Sngie char *ln, *s; 241272343Sngie struct tm tm; 242272343Sngie time_t t; 243272343Sngie int e; 244272343Sngie 245272343Sngie us->size = -1; 246272343Sngie us->atime = us->mtime = 0; 247272343Sngie 248272343Sngie if ((s = strrchr(file, '/')) == NULL) 249272343Sngie s = file; 250272343Sngie else 251272343Sngie ++s; 252272343Sngie 253272343Sngie if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) { 254272343Sngie _ftp_seterr(e); 255272343Sngie return -1; 256272343Sngie } 257272343Sngie for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 258272343Sngie /* nothing */ ; 259272343Sngie for (us->size = 0; *ln && isdigit(*ln); ln++) 260272343Sngie us->size = us->size * 10 + *ln - '0'; 261272343Sngie if (*ln && !isspace(*ln)) { 262272343Sngie _ftp_seterr(FTP_PROTOCOL_ERROR); 263272343Sngie return -1; 264272343Sngie } 265272343Sngie if (us->size == 0) 266272343Sngie us->size = -1; 267272343Sngie DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); 268272343Sngie 269272343Sngie if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) { 270272343Sngie _ftp_seterr(e); 271272343Sngie return -1; 272272343Sngie } 273272343Sngie for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 274272343Sngie /* nothing */ ; 275272343Sngie switch (strspn(ln, "0123456789")) { 276272343Sngie case 14: 277272343Sngie break; 278272343Sngie case 15: 279272343Sngie ln++; 280272343Sngie ln[0] = '2'; 281272343Sngie ln[1] = '0'; 282272343Sngie break; 283272343Sngie default: 284272343Sngie _ftp_seterr(FTP_PROTOCOL_ERROR); 285272343Sngie return -1; 286272343Sngie } 287272343Sngie if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 288272343Sngie &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 289272343Sngie &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { 290272343Sngie _ftp_seterr(FTP_PROTOCOL_ERROR); 291272343Sngie return -1; 292272343Sngie } 293272343Sngie tm.tm_mon--; 294272343Sngie tm.tm_year -= 1900; 295272343Sngie tm.tm_isdst = -1; 296272343Sngie t = timegm(&tm); 297272343Sngie if (t == (time_t)-1) 298272343Sngie t = time(NULL); 299272343Sngie us->mtime = t; 300272343Sngie us->atime = t; 301272343Sngie DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 302272343Sngie "%02d:%02d:%02d\033[m]\n", 303272343Sngie tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 304272343Sngie tm.tm_hour, tm.tm_min, tm.tm_sec)); 305272343Sngie return 0; 306272343Sngie} 307272343Sngie 308272343Sngie/* 309272343Sngie * I/O functions for FTP 310272343Sngie */ 311272343Sngiestruct ftpio { 312272343Sngie int csd; /* Control socket descriptor */ 313272343Sngie int dsd; /* Data socket descriptor */ 314272343Sngie int dir; /* Direction */ 315272343Sngie int eof; /* EOF reached */ 316272343Sngie int err; /* Error code */ 317272343Sngie}; 318272343Sngie 319272343Sngiestatic int _ftp_readfn(void *, char *, int); 320272343Sngiestatic int _ftp_writefn(void *, const char *, int); 321272343Sngiestatic fpos_t _ftp_seekfn(void *, fpos_t, int); 322272343Sngiestatic int _ftp_closefn(void *); 323272343Sngie 324272343Sngiestatic int 325272343Sngie_ftp_readfn(void *v, char *buf, int len) 326272343Sngie{ 327272343Sngie struct ftpio *io; 328272343Sngie int r; 329272343Sngie 330272343Sngie io = (struct ftpio *)v; 331272343Sngie if (io == NULL) { 332272343Sngie errno = EBADF; 333272343Sngie return -1; 334272343Sngie } 335272343Sngie if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) { 336272343Sngie errno = EBADF; 337272343Sngie return -1; 338272343Sngie } 339272343Sngie if (io->err) { 340272343Sngie errno = io->err; 341272343Sngie return -1; 342272343Sngie } 343272343Sngie if (io->eof) 344272343Sngie return 0; 345272343Sngie r = read(io->dsd, buf, len); 346272343Sngie if (r > 0) 347272343Sngie return r; 348272343Sngie if (r == 0) { 349272343Sngie io->eof = 1; 350272343Sngie return _ftp_closefn(v); 351272343Sngie } 352272343Sngie io->err = errno; 353272343Sngie return -1; 354272343Sngie} 355272343Sngie 356static int 357_ftp_writefn(void *v, const char *buf, int len) 358{ 359 struct ftpio *io; 360 int w; 361 362 io = (struct ftpio *)v; 363 if (io == NULL) { 364 errno = EBADF; 365 return -1; 366 } 367 if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) { 368 errno = EBADF; 369 return -1; 370 } 371 if (io->err) { 372 errno = io->err; 373 return -1; 374 } 375 w = write(io->dsd, buf, len); 376 if (w >= 0) 377 return w; 378 io->err = errno; 379 return -1; 380} 381 382static fpos_t 383_ftp_seekfn(void *v, fpos_t pos, int whence) 384{ 385 struct ftpio *io; 386 387 io = (struct ftpio *)v; 388 if (io == NULL) { 389 errno = EBADF; 390 return -1; 391 } 392 errno = ESPIPE; 393 return -1; 394} 395 396static int 397_ftp_closefn(void *v) 398{ 399 struct ftpio *io; 400 int r; 401 402 io = (struct ftpio *)v; 403 if (io == NULL) { 404 errno = EBADF; 405 return -1; 406 } 407 if (io->dir == -1) 408 return 0; 409 if (io->csd == -1 || io->dsd == -1) { 410 errno = EBADF; 411 return -1; 412 } 413 close(io->dsd); 414 io->dir = -1; 415 io->dsd = -1; 416 DEBUG(fprintf(stderr, "Waiting for final status\n")); 417 if ((r = _ftp_chkerr(io->csd)) != FTP_TRANSFER_COMPLETE) 418 io->err = r; 419 else 420 io->err = 0; 421 close(io->csd); 422 io->csd = -1; 423 return io->err ? -1 : 0; 424} 425 426static FILE * 427_ftp_setup(int csd, int dsd, int mode) 428{ 429 struct ftpio *io; 430 FILE *f; 431 432 if ((io = malloc(sizeof *io)) == NULL) 433 return NULL; 434 io->csd = dup(csd); 435 io->dsd = dsd; 436 io->dir = mode; 437 io->eof = io->err = 0; 438 f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn); 439 if (f == NULL) 440 free(io); 441 return f; 442} 443 444/* 445 * Transfer file 446 */ 447static FILE * 448_ftp_transfer(int cd, char *oper, char *file, 449 int mode, off_t offset, char *flags) 450{ 451 struct sockaddr_storage sin; 452 struct sockaddr_in6 *sin6; 453 struct sockaddr_in *sin4; 454 int pasv, high, verbose; 455 int e, sd = -1; 456 socklen_t l; 457 char *s; 458 FILE *df; 459 460 /* check flags */ 461 pasv = (flags && strchr(flags, 'p')); 462 high = (flags && strchr(flags, 'h')); 463 verbose = (flags && strchr(flags, 'v')); 464 465 /* passive mode */ 466 if (!pasv) 467 pasv = ((s = getenv("FTP_PASSIVE_MODE")) == NULL || 468 strncasecmp(s, "no", 2) != 0); 469 470 /* find our own address, bind, and listen */ 471 l = sizeof sin; 472 if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 473 goto sysouch; 474 if (sin.ss_family == AF_INET6) 475 unmappedaddr((struct sockaddr_in6 *)&sin); 476 477 /* open data socket */ 478 if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 479 _fetch_syserr(); 480 return NULL; 481 } 482 483 if (pasv) { 484 u_char addr[64]; 485 char *ln, *p; 486 int i; 487 int port; 488 489 /* send PASV command */ 490 if (verbose) 491 _fetch_info("setting passive mode"); 492 switch (sin.ss_family) { 493 case AF_INET: 494 if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 495 goto ouch; 496 break; 497 case AF_INET6: 498 if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 499 if (e == -1) 500 goto ouch; 501 if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 502 goto ouch; 503 } 504 break; 505 default: 506 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 507 goto ouch; 508 } 509 510 /* 511 * Find address and port number. The reply to the PASV command 512 * is IMHO the one and only weak point in the FTP protocol. 513 */ 514 ln = last_reply; 515 switch (e) { 516 case FTP_PASSIVE_MODE: 517 case FTP_LPASSIVE_MODE: 518 for (p = ln + 3; *p && !isdigit(*p); p++) 519 /* nothing */ ; 520 if (!*p) { 521 e = FTP_PROTOCOL_ERROR; 522 goto ouch; 523 } 524 l = (e == FTP_PASSIVE_MODE ? 6 : 21); 525 for (i = 0; *p && i < l; i++, p++) 526 addr[i] = strtol(p, &p, 10); 527 if (i < l) { 528 e = FTP_PROTOCOL_ERROR; 529 goto ouch; 530 } 531 break; 532 case FTP_EPASSIVE_MODE: 533 for (p = ln + 3; *p && *p != '('; p++) 534 /* nothing */ ; 535 if (!*p) { 536 e = FTP_PROTOCOL_ERROR; 537 goto ouch; 538 } 539 ++p; 540 if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 541 &port, &addr[3]) != 5 || 542 addr[0] != addr[1] || 543 addr[0] != addr[2] || addr[0] != addr[3]) { 544 e = FTP_PROTOCOL_ERROR; 545 goto ouch; 546 } 547 break; 548 } 549 550 /* seek to required offset */ 551 if (offset) 552 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 553 goto sysouch; 554 555 /* construct sockaddr for data socket */ 556 l = sizeof sin; 557 if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 558 goto sysouch; 559 if (sin.ss_family == AF_INET6) 560 unmappedaddr((struct sockaddr_in6 *)&sin); 561 switch (sin.ss_family) { 562 case AF_INET6: 563 sin6 = (struct sockaddr_in6 *)&sin; 564 if (e == FTP_EPASSIVE_MODE) 565 sin6->sin6_port = htons(port); 566 else { 567 bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 568 bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 569 } 570 break; 571 case AF_INET: 572 sin4 = (struct sockaddr_in *)&sin; 573 if (e == FTP_EPASSIVE_MODE) 574 sin4->sin_port = htons(port); 575 else { 576 bcopy(addr, (char *)&sin4->sin_addr, 4); 577 bcopy(addr + 4, (char *)&sin4->sin_port, 2); 578 } 579 break; 580 default: 581 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 582 break; 583 } 584 585 /* connect to data port */ 586 if (verbose) 587 _fetch_info("opening data connection"); 588 if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 589 goto sysouch; 590 591 /* make the server initiate the transfer */ 592 if (verbose) 593 _fetch_info("initiating transfer"); 594 e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 595 if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) 596 goto ouch; 597 598 } else { 599 u_int32_t a; 600 u_short p; 601 int arg, d; 602 char *ap; 603 char hname[INET6_ADDRSTRLEN]; 604 605 switch (sin.ss_family) { 606 case AF_INET6: 607 ((struct sockaddr_in6 *)&sin)->sin6_port = 0; 608#ifdef IPV6_PORTRANGE 609 arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT; 610 if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 611 (char *)&arg, sizeof(arg)) == -1) 612 goto sysouch; 613#endif 614 break; 615 case AF_INET: 616 ((struct sockaddr_in *)&sin)->sin_port = 0; 617 arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; 618 if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 619 (char *)&arg, sizeof arg) == -1) 620 goto sysouch; 621 break; 622 } 623 if (verbose) 624 _fetch_info("binding data socket"); 625 if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 626 goto sysouch; 627 if (listen(sd, 1) == -1) 628 goto sysouch; 629 630 /* find what port we're on and tell the server */ 631 if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 632 goto sysouch; 633 switch (sin.ss_family) { 634 case AF_INET: 635 sin4 = (struct sockaddr_in *)&sin; 636 a = ntohl(sin4->sin_addr.s_addr); 637 p = ntohs(sin4->sin_port); 638 e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 639 (a >> 24) & 0xff, (a >> 16) & 0xff, 640 (a >> 8) & 0xff, a & 0xff, 641 (p >> 8) & 0xff, p & 0xff); 642 break; 643 case AF_INET6: 644#define UC(b) (((int)b)&0xff) 645 e = -1; 646 sin6 = (struct sockaddr_in6 *)&sin; 647 if (getnameinfo((struct sockaddr *)&sin, sin.ss_len, 648 hname, sizeof(hname), 649 NULL, 0, NI_NUMERICHOST) == 0) { 650 e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 651 htons(sin6->sin6_port)); 652 if (e == -1) 653 goto ouch; 654 } 655 if (e != FTP_OK) { 656 ap = (char *)&sin6->sin6_addr; 657 e = _ftp_cmd(cd, 658 "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 659 6, 16, 660 UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 661 UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 662 UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 663 UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 664 2, 665 (ntohs(sin6->sin6_port) >> 8) & 0xff, 666 ntohs(sin6->sin6_port) & 0xff); 667 } 668 break; 669 default: 670 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 671 goto ouch; 672 } 673 if (e != FTP_OK) 674 goto ouch; 675 676 /* seek to required offset */ 677 if (offset) 678 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 679 goto sysouch; 680 681 /* make the server initiate the transfer */ 682 if (verbose) 683 _fetch_info("initiating transfer"); 684 e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 685 if (e != FTP_OPEN_DATA_CONNECTION) 686 goto ouch; 687 688 /* accept the incoming connection and go to town */ 689 if ((d = accept(sd, NULL, NULL)) == -1) 690 goto sysouch; 691 close(sd); 692 sd = d; 693 } 694 695 if ((df = _ftp_setup(cd, sd, mode)) == NULL) 696 goto sysouch; 697 return df; 698 699sysouch: 700 _fetch_syserr(); 701 if (sd >= 0) 702 close(sd); 703 return NULL; 704 705ouch: 706 if (e != -1) 707 _ftp_seterr(e); 708 if (sd >= 0) 709 close(sd); 710 return NULL; 711} 712 713/* 714 * Return default port 715 */ 716static int 717_ftp_default_port(void) 718{ 719 struct servent *se; 720 721 if ((se = getservbyname(SCHEME_FTP, "tcp")) != NULL) 722 return ntohs(se->s_port); 723 return FTP_DEFAULT_PORT; 724} 725 726/* 727 * Log on to FTP server 728 */ 729static int 730_ftp_connect(struct url *url, struct url *purl, char *flags) 731{ 732 int cd, e, direct, verbose; 733#ifdef INET6 734 int af = AF_UNSPEC; 735#else 736 int af = AF_INET; 737#endif 738 const char *logname; 739 char *user, *pwd; 740 char localhost[MAXHOSTNAMELEN]; 741 char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 742 743 direct = (flags && strchr(flags, 'd')); 744 verbose = (flags && strchr(flags, 'v')); 745 if ((flags && strchr(flags, '4'))) 746 af = AF_INET; 747 else if ((flags && strchr(flags, '6'))) 748 af = AF_INET6; 749 750 if (direct) 751 purl = NULL; 752 753 /* check for proxy */ 754 if (purl) { 755 /* XXX proxy authentication! */ 756 cd = _fetch_connect(purl->host, purl->port, af, verbose); 757 } else { 758 /* no proxy, go straight to target */ 759 cd = _fetch_connect(url->host, url->port, af, verbose); 760 purl = NULL; 761 } 762 763 /* check connection */ 764 if (cd == -1) { 765 _fetch_syserr(); 766 return NULL; 767 } 768 769 /* expect welcome message */ 770 if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 771 goto fouch; 772 773 /* XXX FTP_AUTH, and maybe .netrc */ 774 775 /* send user name and password */ 776 user = url->user; 777 if (!user || !*user) 778 user = FTP_ANONYMOUS_USER; 779 if (purl && url->port == FTP_DEFAULT_PORT) 780 e = _ftp_cmd(cd, "USER %s@%s", user, url->host); 781 else if (purl) 782 e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port); 783 else 784 e = _ftp_cmd(cd, "USER %s", user); 785 786 /* did the server request a password? */ 787 if (e == FTP_NEED_PASSWORD) { 788 pwd = url->pwd; 789 if (!pwd || !*pwd) 790 pwd = getenv("FTP_PASSWORD"); 791 if (!pwd || !*pwd) { 792 if ((logname = getlogin()) == 0) 793 logname = FTP_ANONYMOUS_PASSWORD; 794 gethostname(localhost, sizeof localhost); 795 snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost); 796 pwd = pbuf; 797 } 798 e = _ftp_cmd(cd, "PASS %s", pwd); 799 } 800 801 /* did the server request an account? */ 802 if (e == FTP_NEED_ACCOUNT) 803 goto fouch; 804 805 /* we should be done by now */ 806 if (e != FTP_LOGGED_IN) 807 goto fouch; 808 809 /* might as well select mode and type at once */ 810#ifdef FTP_FORCE_STREAM_MODE 811 if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 812 goto fouch; 813#endif 814 if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 815 goto fouch; 816 817 /* done */ 818 return cd; 819 820fouch: 821 if (e != -1) 822 _ftp_seterr(e); 823 close(cd); 824 return NULL; 825} 826 827/* 828 * Disconnect from server 829 */ 830static void 831_ftp_disconnect(int cd) 832{ 833 (void)_ftp_cmd(cd, "QUIT"); 834 close(cd); 835} 836 837/* 838 * Check if we're already connected 839 */ 840static int 841_ftp_isconnected(struct url *url) 842{ 843 return (cached_socket 844 && (strcmp(url->host, cached_host.host) == 0) 845 && (strcmp(url->user, cached_host.user) == 0) 846 && (strcmp(url->pwd, cached_host.pwd) == 0) 847 && (url->port == cached_host.port)); 848} 849 850/* 851 * Check the cache, reconnect if no luck 852 */ 853static int 854_ftp_cached_connect(struct url *url, struct url *purl, char *flags) 855{ 856 int e, cd; 857 858 cd = -1; 859 860 /* set default port */ 861 if (!url->port) 862 url->port = _ftp_default_port(); 863 864 /* try to use previously cached connection */ 865 if (_ftp_isconnected(url)) { 866 e = _ftp_cmd(cached_socket, "NOOP"); 867 if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 868 return cached_socket; 869 } 870 871 /* connect to server */ 872 if ((cd = _ftp_connect(url, purl, flags)) == -1) 873 return -1; 874 if (cached_socket) 875 _ftp_disconnect(cached_socket); 876 cached_socket = cd; 877 memcpy(&cached_host, url, sizeof *url); 878 return cd; 879} 880 881/* 882 * Check the proxy settings 883 */ 884static struct url * 885_ftp_get_proxy(void) 886{ 887 struct url *purl; 888 char *p; 889 890 if (((p = getenv("FTP_PROXY")) || (p = getenv("HTTP_PROXY"))) && 891 *p && (purl = fetchParseURL(p)) != NULL) { 892 if (!*purl->scheme) 893 strcpy(purl->scheme, SCHEME_FTP); 894 if (!purl->port) 895 purl->port = _ftp_default_port(); 896 if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 897 strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 898 return purl; 899 fetchFreeURL(purl); 900 } 901 return NULL; 902} 903 904/* 905 * Get and stat file 906 */ 907FILE * 908fetchXGetFTP(struct url *url, struct url_stat *us, char *flags) 909{ 910 struct url *purl; 911 int cd; 912 913 /* get the proxy URL, and check if we should use HTTP instead */ 914 if (!(flags && strchr(flags, 'd')) && (purl = _ftp_get_proxy()) != NULL) { 915 if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 916 return _http_request(url, "GET", us, purl, flags); 917 } else { 918 purl = NULL; 919 } 920 921 /* connect to server */ 922 cd = _ftp_cached_connect(url, purl, flags); 923 if (purl) 924 fetchFreeURL(purl); 925 if (cd == NULL) 926 return NULL; 927 928 /* change directory */ 929 if (_ftp_cwd(cd, url->doc) == -1) 930 return NULL; 931 932 /* stat file */ 933 if (us && _ftp_stat(cd, url->doc, us) == -1 934 && fetchLastErrCode != FETCH_PROTO 935 && fetchLastErrCode != FETCH_UNAVAIL) 936 return NULL; 937 938 /* initiate the transfer */ 939 return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags); 940} 941 942/* 943 * Get file 944 */ 945FILE * 946fetchGetFTP(struct url *url, char *flags) 947{ 948 return fetchXGetFTP(url, NULL, flags); 949} 950 951/* 952 * Put file 953 */ 954FILE * 955fetchPutFTP(struct url *url, char *flags) 956{ 957 struct url *purl; 958 int cd; 959 960 /* get the proxy URL, and check if we should use HTTP instead */ 961 if (!(flags && strchr(flags, 'd')) && (purl = _ftp_get_proxy()) != NULL) { 962 if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 963 /* XXX HTTP PUT is not implemented, so try without the proxy */ 964 purl = NULL; 965 } else { 966 purl = NULL; 967 } 968 969 /* connect to server */ 970 cd = _ftp_cached_connect(url, purl, flags); 971 if (purl) 972 fetchFreeURL(purl); 973 if (cd == NULL) 974 return NULL; 975 976 /* change directory */ 977 if (_ftp_cwd(cd, url->doc) == -1) 978 return NULL; 979 980 /* initiate the transfer */ 981 return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR", 982 url->doc, O_WRONLY, url->offset, flags); 983} 984 985/* 986 * Get file stats 987 */ 988int 989fetchStatFTP(struct url *url, struct url_stat *us, char *flags) 990{ 991 struct url *purl; 992 int cd; 993 994 /* get the proxy URL, and check if we should use HTTP instead */ 995 if (!(flags && strchr(flags, 'd')) && (purl = _ftp_get_proxy()) != NULL) { 996 if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) { 997 FILE *f; 998 999 if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL) 1000 return -1; 1001 fclose(f); 1002 return 0; 1003 } 1004 } else { 1005 purl = NULL; 1006 } 1007 1008 /* connect to server */ 1009 cd = _ftp_cached_connect(url, purl, flags); 1010 if (purl) 1011 fetchFreeURL(purl); 1012 if (cd == NULL) 1013 return NULL; 1014 1015 /* change directory */ 1016 if (_ftp_cwd(cd, url->doc) == -1) 1017 return -1; 1018 1019 /* stat file */ 1020 return _ftp_stat(cd, url->doc, us); 1021} 1022 1023/* 1024 * List a directory 1025 */ 1026extern void warnx(char *, ...); 1027struct url_ent * 1028fetchListFTP(struct url *url, char *flags) 1029{ 1030 warnx("fetchListFTP(): not implemented"); 1031 return NULL; 1032} 1033