ftp.c revision 69041
1139778Simp/*- 212115Sdyson * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav 312115Sdyson * All rights reserved. 412115Sdyson * 512115Sdyson * Redistribution and use in source and binary forms, with or without 612115Sdyson * modification, are permitted provided that the following conditions 7139778Simp * are met: 812115Sdyson * 1. Redistributions of source code must retain the above copyright 912115Sdyson * notice, this list of conditions and the following disclaimer 1012115Sdyson * in this position and unchanged. 1112115Sdyson * 2. Redistributions in binary form must reproduce the above copyright 1212115Sdyson * notice, this list of conditions and the following disclaimer in the 1312115Sdyson * documentation and/or other materials provided with the distribution. 1412115Sdyson * 3. The name of the author may not be used to endorse or promote products 1512115Sdyson * derived from this software without specific prior written permission 1612115Sdyson * 1712115Sdyson * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 1812115Sdyson * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 1912115Sdyson * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 2012115Sdyson * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 2112115Sdyson * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 2212115Sdyson * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 2312115Sdyson * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 2412115Sdyson * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2512115Sdyson * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 2612115Sdyson * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2712115Sdyson * 2812115Sdyson * $FreeBSD: head/lib/libfetch/ftp.c 69041 2000-11-22 14:30:28Z des $ 2912115Sdyson */ 3012115Sdyson 3112115Sdyson/* 3212115Sdyson * Portions of this code were taken from or based on ftpio.c: 3312115Sdyson * 3412115Sdyson * ---------------------------------------------------------------------------- 3593015Sbde * "THE BEER-WARE LICENSE" (Revision 42): 3660041Sphk * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you 3712115Sdyson * can do whatever you want with this stuff. If we meet some day, and you think 3812115Sdyson * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 3912115Sdyson * ---------------------------------------------------------------------------- 4012115Sdyson * 4176166Smarkm * Major Changelog: 4276166Smarkm * 4376166Smarkm * Dag-Erling Co�dan Sm�rgrav 4476166Smarkm * 9 Jun 1998 4531561Sbde * 4669517Sbde * Incorporated into libfetch 4712115Sdyson * 4876166Smarkm * Jordan K. Hubbard 49202283Slulf * 17 Jan 1996 50202283Slulf * 51202283Slulf * Turned inside out. Now returns xfers as new file ids, not as a special 52202283Slulf * `state' of FTP_t 53255183Spfg * 54255183Spfg * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $ 55255183Spfg * 5676166Smarkm */ 57131925Smarcel 5892728Salfred#include <sys/param.h> 5941591Sarchie#include <sys/socket.h> 6033291Sbde#include <netinet/in.h> 6112115Sdyson 6212115Sdyson#include <ctype.h> 6312115Sdyson#include <errno.h> 6412115Sdyson#include <fcntl.h> 6512115Sdyson#include <netdb.h> 6612115Sdyson#include <stdarg.h> 67247209Spfg#include <stdio.h> 6812115Sdyson#include <stdlib.h> 6912115Sdyson#include <string.h> 70202283Slulf#include <time.h> 7112115Sdyson#include <unistd.h> 72252234Spfg 7312115Sdyson#include "fetch.h" 74255183Spfg#include "common.h" 75255183Spfg#include "ftperr.h" 76255183Spfg 7712115Sdyson#define FTP_ANONYMOUS_USER "ftp" 7830474Sphk#define FTP_ANONYMOUS_PASSWORD "ftp" 7912115Sdyson 8030474Sphk#define FTP_CONNECTION_ALREADY_OPEN 125 8112115Sdyson#define FTP_OPEN_DATA_CONNECTION 150 82255183Spfg#define FTP_OK 200 8312115Sdyson#define FTP_FILE_STATUS 213 84255183Spfg#define FTP_SERVICE_READY 220 85261312Spfg#define FTP_TRANSFER_COMPLETE 226 86261312Spfg#define FTP_PASSIVE_MODE 227 87255183Spfg#define FTP_LPASSIVE_MODE 228 88261312Spfg#define FTP_EPASSIVE_MODE 229 89255183Spfg#define FTP_LOGGED_IN 230 90255183Spfg#define FTP_FILE_ACTION_OK 250 91255183Spfg#define FTP_NEED_PASSWORD 331 92255183Spfg#define FTP_NEED_ACCOUNT 332 93255183Spfg#define FTP_FILE_OK 350 94255183Spfg#define FTP_SYNTAX_ERROR 500 95255183Spfg#define FTP_PROTOCOL_ERROR 999 96255183Spfg 97255183Spfgstatic struct url cached_host; 98255183Spfgstatic int cached_socket; 99255183Spfg 100255183Spfgstatic char *last_reply; 101255183Spfgstatic size_t lr_size, lr_length; 102255183Spfgstatic int last_code; 103255183Spfg 104255183Spfg#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 105255183Spfg && isdigit(foo[2]) \ 106255183Spfg && (foo[3] == ' ' || foo[3] == '\0')) 10712115Sdyson#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 10812115Sdyson && isdigit(foo[2]) && foo[3] == '-') 10912115Sdyson 11030474Sphk/* translate IPv4 mapped IPv6 address to IPv4 address */ 11130474Sphkstatic void 112255183Spfgunmappedaddr(struct sockaddr_in6 *sin6) 113261312Spfg{ 114255183Spfg struct sockaddr_in *sin4; 115255183Spfg u_int32_t addr; 116255183Spfg int port; 11730474Sphk 11812115Sdyson if (sin6->sin6_family != AF_INET6 || 119255183Spfg !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 120255183Spfg return; 121255183Spfg sin4 = (struct sockaddr_in *)sin6; 122255183Spfg addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 123255183Spfg port = sin6->sin6_port; 124255183Spfg memset(sin4, 0, sizeof(struct sockaddr_in)); 125255183Spfg sin4->sin_addr.s_addr = addr; 126255183Spfg sin4->sin_port = port; 127255183Spfg sin4->sin_family = AF_INET; 128255183Spfg sin4->sin_len = sizeof(struct sockaddr_in); 129255183Spfg} 130255183Spfg 13112115Sdyson/* 13212115Sdyson * Get server response 133131925Smarcel */ 13442539Seivindstatic int 135247209Spfg_ftp_chkerr(int cd) 13612115Sdyson{ 13796752Siedowse if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 138255188Spfg _fetch_syserr(); 13912115Sdyson return -1; 14012115Sdyson } 14112115Sdyson if (isftpinfo(last_reply)) { 14212115Sdyson while (!isftpreply(last_reply)) { 14312115Sdyson switch (_fetch_getln(cd, &last_reply, &lr_size, &lr_length)) { 14412115Sdyson case -1: 145136991Sphk _fetch_syserr(); 14612115Sdyson return -1; 147217582Sjhb case 0: 14812115Sdyson _ftp_seterr(FTP_PROTOCOL_ERROR); 14912115Sdyson return -1; 15012115Sdyson } 15112115Sdyson } 15212115Sdyson } 153255188Spfg 154255188Spfg while (lr_length && isspace(last_reply[lr_length-1])) 155255188Spfg lr_length--; 156255188Spfg last_reply[lr_length] = 0; 15712115Sdyson 15812115Sdyson if (!isftpreply(last_reply)) { 159131925Smarcel _ftp_seterr(FTP_PROTOCOL_ERROR); 160229549Spfg return -1; 161229549Spfg } 162229549Spfg 163229549Spfg last_code = (last_reply[0] - '0') * 100 164229549Spfg + (last_reply[1] - '0') * 10 165229549Spfg + (last_reply[2] - '0'); 166229549Spfg 167229549Spfg return last_code; 168229549Spfg} 169229549Spfg 170229549Spfg/* 171229549Spfg * Send a command and check reply 172229549Spfg */ 173229549Spfgstatic int 174229549Spfg_ftp_cmd(int cd, char *fmt, ...) 175229549Spfg{ 176229549Spfg va_list ap; 177229549Spfg size_t len; 178229549Spfg char *msg; 179229549Spfg int r; 180229549Spfg 181229549Spfg va_start(ap, fmt); 182229549Spfg len = vasprintf(&msg, fmt, ap); 183229549Spfg va_end(ap); 184229549Spfg 185229549Spfg if (msg == NULL) { 186229549Spfg errno = ENOMEM; 187229549Spfg _fetch_syserr(); 188229549Spfg return -1; 189229549Spfg } 190229549Spfg 191229549Spfg r = _fetch_putln(cd, msg, len); 192229549Spfg free(msg); 193229549Spfg 194229549Spfg if (r == -1) { 195229549Spfg _fetch_syserr(); 196229549Spfg return -1; 197229549Spfg } 198229549Spfg 199229549Spfg return _ftp_chkerr(cd); 200229549Spfg} 201229549Spfg 202229549Spfg/* 203229549Spfg * Return a pointer to the filename part of a path 204229549Spfg */ 205229549Spfgstatic char * 206229549Spfg_ftp_filename(char *file) 207229549Spfg{ 208229549Spfg char *s; 209229549Spfg 210229549Spfg if ((s = strrchr(file, '/')) == NULL) 211229549Spfg return file; 212229549Spfg else 213229549Spfg return s + 1; 214229549Spfg} 215229549Spfg 216229549Spfg/* 217229549Spfg * Change working directory to the directory that contains the 218229549Spfg * specified file. 219229549Spfg */ 220229549Spfgstatic int 221229549Spfg_ftp_cwd(int cd, char *file) 222229549Spfg{ 223229549Spfg char *s; 224229549Spfg int e; 225229549Spfg 226229549Spfg if ((s = strrchr(file, '/')) == NULL || s == file) { 227229549Spfg e = _ftp_cmd(cd, "CWD /"); 228229549Spfg } else { 229229549Spfg e = _ftp_cmd(cd, "CWD %.*s", s - file, file); 230229549Spfg } 231229549Spfg if (e != FTP_FILE_ACTION_OK) { 232229549Spfg _ftp_seterr(e); 233229549Spfg return -1; 234229549Spfg } 235229549Spfg return 0; 236229549Spfg} 237229549Spfg 238229549Spfg/* 239229549Spfg * Request and parse file stats 240229549Spfg */ 241229549Spfgstatic int 242229549Spfg_ftp_stat(int cd, char *file, struct url_stat *us) 243229549Spfg{ 244229549Spfg char *ln, *s; 245229549Spfg struct tm tm; 246229549Spfg time_t t; 247229549Spfg int e; 248229549Spfg 249229549Spfg us->size = -1; 250229549Spfg us->atime = us->mtime = 0; 251229549Spfg 252229549Spfg if ((s = strrchr(file, '/')) == NULL) 253229549Spfg s = file; 254229549Spfg else 255229549Spfg ++s; 256229549Spfg 257229549Spfg if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) { 258229549Spfg _ftp_seterr(e); 259229549Spfg return -1; 260229549Spfg } 261229549Spfg for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 262229549Spfg /* nothing */ ; 263229549Spfg for (us->size = 0; *ln && isdigit(*ln); ln++) 264 us->size = us->size * 10 + *ln - '0'; 265 if (*ln && !isspace(*ln)) { 266 _ftp_seterr(FTP_PROTOCOL_ERROR); 267 return -1; 268 } 269 if (us->size == 0) 270 us->size = -1; 271 DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); 272 273 if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) { 274 _ftp_seterr(e); 275 return -1; 276 } 277 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 278 /* nothing */ ; 279 switch (strspn(ln, "0123456789")) { 280 case 14: 281 break; 282 case 15: 283 ln++; 284 ln[0] = '2'; 285 ln[1] = '0'; 286 break; 287 default: 288 _ftp_seterr(FTP_PROTOCOL_ERROR); 289 return -1; 290 } 291 if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 292 &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 293 &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { 294 _ftp_seterr(FTP_PROTOCOL_ERROR); 295 return -1; 296 } 297 tm.tm_mon--; 298 tm.tm_year -= 1900; 299 tm.tm_isdst = -1; 300 t = timegm(&tm); 301 if (t == (time_t)-1) 302 t = time(NULL); 303 us->mtime = t; 304 us->atime = t; 305 DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 306 "%02d:%02d:%02d\033[m]\n", 307 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 308 tm.tm_hour, tm.tm_min, tm.tm_sec)); 309 return 0; 310} 311 312/* 313 * I/O functions for FTP 314 */ 315struct ftpio { 316 int csd; /* Control socket descriptor */ 317 int dsd; /* Data socket descriptor */ 318 int dir; /* Direction */ 319 int eof; /* EOF reached */ 320 int err; /* Error code */ 321}; 322 323static int _ftp_readfn(void *, char *, int); 324static int _ftp_writefn(void *, const char *, int); 325static fpos_t _ftp_seekfn(void *, fpos_t, int); 326static int _ftp_closefn(void *); 327 328static int 329_ftp_readfn(void *v, char *buf, int len) 330{ 331 struct ftpio *io; 332 int r; 333 334 io = (struct ftpio *)v; 335 if (io == NULL) { 336 errno = EBADF; 337 return -1; 338 } 339 if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) { 340 errno = EBADF; 341 return -1; 342 } 343 if (io->err) { 344 errno = io->err; 345 return -1; 346 } 347 if (io->eof) 348 return 0; 349 r = read(io->dsd, buf, len); 350 if (r > 0) 351 return r; 352 if (r == 0) { 353 io->eof = 1; 354 return _ftp_closefn(v); 355 } 356 io->err = errno; 357 return -1; 358} 359 360static int 361_ftp_writefn(void *v, const char *buf, int len) 362{ 363 struct ftpio *io; 364 int w; 365 366 io = (struct ftpio *)v; 367 if (io == NULL) { 368 errno = EBADF; 369 return -1; 370 } 371 if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) { 372 errno = EBADF; 373 return -1; 374 } 375 if (io->err) { 376 errno = io->err; 377 return -1; 378 } 379 w = write(io->dsd, buf, len); 380 if (w >= 0) 381 return w; 382 io->err = errno; 383 return -1; 384} 385 386static fpos_t 387_ftp_seekfn(void *v, fpos_t pos, int whence) 388{ 389 struct ftpio *io; 390 391 io = (struct ftpio *)v; 392 if (io == NULL) { 393 errno = EBADF; 394 return -1; 395 } 396 errno = ESPIPE; 397 return -1; 398} 399 400static int 401_ftp_closefn(void *v) 402{ 403 struct ftpio *io; 404 int r; 405 406 io = (struct ftpio *)v; 407 if (io == NULL) { 408 errno = EBADF; 409 return -1; 410 } 411 if (io->dir == -1) 412 return 0; 413 if (io->csd == -1 || io->dsd == -1) { 414 errno = EBADF; 415 return -1; 416 } 417 close(io->dsd); 418 io->dir = -1; 419 io->dsd = -1; 420 DEBUG(fprintf(stderr, "Waiting for final status\n")); 421 if ((r = _ftp_chkerr(io->csd)) != FTP_TRANSFER_COMPLETE) 422 io->err = r; 423 else 424 io->err = 0; 425 close(io->csd); 426 io->csd = -1; 427 return io->err ? -1 : 0; 428} 429 430static FILE * 431_ftp_setup(int csd, int dsd, int mode) 432{ 433 struct ftpio *io; 434 FILE *f; 435 436 if ((io = malloc(sizeof *io)) == NULL) 437 return NULL; 438 io->csd = dup(csd); 439 io->dsd = dsd; 440 io->dir = mode; 441 io->eof = io->err = 0; 442 f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn); 443 if (f == NULL) 444 free(io); 445 return f; 446} 447 448/* 449 * Transfer file 450 */ 451static FILE * 452_ftp_transfer(int cd, char *oper, char *file, 453 int mode, off_t offset, char *flags) 454{ 455 struct sockaddr_storage sin; 456 struct sockaddr_in6 *sin6; 457 struct sockaddr_in *sin4; 458 int pasv, high, verbose; 459 int e, sd = -1; 460 socklen_t l; 461 char *s; 462 FILE *df; 463 464 /* check flags */ 465 pasv = CHECK_FLAG('p'); 466 high = CHECK_FLAG('h'); 467 verbose = CHECK_FLAG('v'); 468 469 /* passive mode */ 470 if (!pasv) 471 pasv = ((s = getenv("FTP_PASSIVE_MODE")) == NULL || 472 strncasecmp(s, "no", 2) != 0); 473 474 /* find our own address, bind, and listen */ 475 l = sizeof sin; 476 if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 477 goto sysouch; 478 if (sin.ss_family == AF_INET6) 479 unmappedaddr((struct sockaddr_in6 *)&sin); 480 481 /* open data socket */ 482 if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 483 _fetch_syserr(); 484 return NULL; 485 } 486 487 if (pasv) { 488 u_char addr[64]; 489 char *ln, *p; 490 int i; 491 int port; 492 493 /* send PASV command */ 494 if (verbose) 495 _fetch_info("setting passive mode"); 496 switch (sin.ss_family) { 497 case AF_INET: 498 if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 499 goto ouch; 500 break; 501 case AF_INET6: 502 if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 503 if (e == -1) 504 goto ouch; 505 if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 506 goto ouch; 507 } 508 break; 509 default: 510 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 511 goto ouch; 512 } 513 514 /* 515 * Find address and port number. The reply to the PASV command 516 * is IMHO the one and only weak point in the FTP protocol. 517 */ 518 ln = last_reply; 519 switch (e) { 520 case FTP_PASSIVE_MODE: 521 case FTP_LPASSIVE_MODE: 522 for (p = ln + 3; *p && !isdigit(*p); p++) 523 /* nothing */ ; 524 if (!*p) { 525 e = FTP_PROTOCOL_ERROR; 526 goto ouch; 527 } 528 l = (e == FTP_PASSIVE_MODE ? 6 : 21); 529 for (i = 0; *p && i < l; i++, p++) 530 addr[i] = strtol(p, &p, 10); 531 if (i < l) { 532 e = FTP_PROTOCOL_ERROR; 533 goto ouch; 534 } 535 break; 536 case FTP_EPASSIVE_MODE: 537 for (p = ln + 3; *p && *p != '('; p++) 538 /* nothing */ ; 539 if (!*p) { 540 e = FTP_PROTOCOL_ERROR; 541 goto ouch; 542 } 543 ++p; 544 if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 545 &port, &addr[3]) != 5 || 546 addr[0] != addr[1] || 547 addr[0] != addr[2] || addr[0] != addr[3]) { 548 e = FTP_PROTOCOL_ERROR; 549 goto ouch; 550 } 551 break; 552 } 553 554 /* seek to required offset */ 555 if (offset) 556 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 557 goto sysouch; 558 559 /* construct sockaddr for data socket */ 560 l = sizeof sin; 561 if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 562 goto sysouch; 563 if (sin.ss_family == AF_INET6) 564 unmappedaddr((struct sockaddr_in6 *)&sin); 565 switch (sin.ss_family) { 566 case AF_INET6: 567 sin6 = (struct sockaddr_in6 *)&sin; 568 if (e == FTP_EPASSIVE_MODE) 569 sin6->sin6_port = htons(port); 570 else { 571 bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 572 bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 573 } 574 break; 575 case AF_INET: 576 sin4 = (struct sockaddr_in *)&sin; 577 if (e == FTP_EPASSIVE_MODE) 578 sin4->sin_port = htons(port); 579 else { 580 bcopy(addr, (char *)&sin4->sin_addr, 4); 581 bcopy(addr + 4, (char *)&sin4->sin_port, 2); 582 } 583 break; 584 default: 585 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 586 break; 587 } 588 589 /* connect to data port */ 590 if (verbose) 591 _fetch_info("opening data connection"); 592 if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 593 goto sysouch; 594 595 /* make the server initiate the transfer */ 596 if (verbose) 597 _fetch_info("initiating transfer"); 598 e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 599 if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) 600 goto ouch; 601 602 } else { 603 u_int32_t a; 604 u_short p; 605 int arg, d; 606 char *ap; 607 char hname[INET6_ADDRSTRLEN]; 608 609 switch (sin.ss_family) { 610 case AF_INET6: 611 ((struct sockaddr_in6 *)&sin)->sin6_port = 0; 612#ifdef IPV6_PORTRANGE 613 arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT; 614 if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 615 (char *)&arg, sizeof(arg)) == -1) 616 goto sysouch; 617#endif 618 break; 619 case AF_INET: 620 ((struct sockaddr_in *)&sin)->sin_port = 0; 621 arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; 622 if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 623 (char *)&arg, sizeof arg) == -1) 624 goto sysouch; 625 break; 626 } 627 if (verbose) 628 _fetch_info("binding data socket"); 629 if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 630 goto sysouch; 631 if (listen(sd, 1) == -1) 632 goto sysouch; 633 634 /* find what port we're on and tell the server */ 635 if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 636 goto sysouch; 637 switch (sin.ss_family) { 638 case AF_INET: 639 sin4 = (struct sockaddr_in *)&sin; 640 a = ntohl(sin4->sin_addr.s_addr); 641 p = ntohs(sin4->sin_port); 642 e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 643 (a >> 24) & 0xff, (a >> 16) & 0xff, 644 (a >> 8) & 0xff, a & 0xff, 645 (p >> 8) & 0xff, p & 0xff); 646 break; 647 case AF_INET6: 648#define UC(b) (((int)b)&0xff) 649 e = -1; 650 sin6 = (struct sockaddr_in6 *)&sin; 651 if (getnameinfo((struct sockaddr *)&sin, sin.ss_len, 652 hname, sizeof(hname), 653 NULL, 0, NI_NUMERICHOST) == 0) { 654 e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 655 htons(sin6->sin6_port)); 656 if (e == -1) 657 goto ouch; 658 } 659 if (e != FTP_OK) { 660 ap = (char *)&sin6->sin6_addr; 661 e = _ftp_cmd(cd, 662 "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 663 6, 16, 664 UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 665 UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 666 UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 667 UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 668 2, 669 (ntohs(sin6->sin6_port) >> 8) & 0xff, 670 ntohs(sin6->sin6_port) & 0xff); 671 } 672 break; 673 default: 674 e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 675 goto ouch; 676 } 677 if (e != FTP_OK) 678 goto ouch; 679 680 /* seek to required offset */ 681 if (offset) 682 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 683 goto sysouch; 684 685 /* make the server initiate the transfer */ 686 if (verbose) 687 _fetch_info("initiating transfer"); 688 e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 689 if (e != FTP_OPEN_DATA_CONNECTION) 690 goto ouch; 691 692 /* accept the incoming connection and go to town */ 693 if ((d = accept(sd, NULL, NULL)) == -1) 694 goto sysouch; 695 close(sd); 696 sd = d; 697 } 698 699 if ((df = _ftp_setup(cd, sd, mode)) == NULL) 700 goto sysouch; 701 return df; 702 703sysouch: 704 _fetch_syserr(); 705 if (sd >= 0) 706 close(sd); 707 return NULL; 708 709ouch: 710 if (e != -1) 711 _ftp_seterr(e); 712 if (sd >= 0) 713 close(sd); 714 return NULL; 715} 716 717/* 718 * Log on to FTP server 719 */ 720static int 721_ftp_connect(struct url *url, struct url *purl, char *flags) 722{ 723 int cd, e, direct, verbose; 724#ifdef INET6 725 int af = AF_UNSPEC; 726#else 727 int af = AF_INET; 728#endif 729 const char *logname; 730 char *user, *pwd; 731 char localhost[MAXHOSTNAMELEN]; 732 char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 733 734 direct = CHECK_FLAG('d'); 735 verbose = CHECK_FLAG('v'); 736 if (CHECK_FLAG('4')) 737 af = AF_INET; 738 else if (CHECK_FLAG('6')) 739 af = AF_INET6; 740 741 if (direct) 742 purl = NULL; 743 744 /* check for proxy */ 745 if (purl) { 746 /* XXX proxy authentication! */ 747 cd = _fetch_connect(purl->host, purl->port, af, verbose); 748 } else { 749 /* no proxy, go straight to target */ 750 cd = _fetch_connect(url->host, url->port, af, verbose); 751 purl = NULL; 752 } 753 754 /* check connection */ 755 if (cd == -1) { 756 _fetch_syserr(); 757 return NULL; 758 } 759 760 /* expect welcome message */ 761 if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 762 goto fouch; 763 764 /* XXX FTP_AUTH, and maybe .netrc */ 765 766 /* send user name and password */ 767 user = url->user; 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_PASSWORD; 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("HTTP_PROXY"))) && 882 *p && (purl = fetchParseURL(p)) != NULL) { 883 if (!*purl->scheme) 884 strcpy(purl->scheme, SCHEME_HTTP); 885 if (!purl->port) 886 purl->port = _fetch_default_proxy_port(purl->scheme); 887 if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 888 strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 889 return purl; 890 fetchFreeURL(purl); 891 } 892 return NULL; 893} 894 895/* 896 * Get and stat file 897 */ 898FILE * 899fetchXGetFTP(struct url *url, struct url_stat *us, char *flags) 900{ 901 struct url *purl; 902 int cd; 903 904 /* get the proxy URL, and check if we should use HTTP instead */ 905 if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 906 if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 907 return _http_request(url, "GET", us, purl, flags); 908 } else { 909 purl = NULL; 910 } 911 912 /* connect to server */ 913 cd = _ftp_cached_connect(url, purl, flags); 914 if (purl) 915 fetchFreeURL(purl); 916 if (cd == NULL) 917 return NULL; 918 919 /* change directory */ 920 if (_ftp_cwd(cd, url->doc) == -1) 921 return NULL; 922 923 /* stat file */ 924 if (us && _ftp_stat(cd, url->doc, us) == -1 925 && fetchLastErrCode != FETCH_PROTO 926 && fetchLastErrCode != FETCH_UNAVAIL) 927 return NULL; 928 929 /* initiate the transfer */ 930 return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags); 931} 932 933/* 934 * Get file 935 */ 936FILE * 937fetchGetFTP(struct url *url, char *flags) 938{ 939 return fetchXGetFTP(url, NULL, flags); 940} 941 942/* 943 * Put file 944 */ 945FILE * 946fetchPutFTP(struct url *url, char *flags) 947{ 948 struct url *purl; 949 int cd; 950 951 /* get the proxy URL, and check if we should use HTTP instead */ 952 if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 953 if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 954 /* XXX HTTP PUT is not implemented, so try without the proxy */ 955 purl = NULL; 956 } else { 957 purl = NULL; 958 } 959 960 /* connect to server */ 961 cd = _ftp_cached_connect(url, purl, flags); 962 if (purl) 963 fetchFreeURL(purl); 964 if (cd == NULL) 965 return NULL; 966 967 /* change directory */ 968 if (_ftp_cwd(cd, url->doc) == -1) 969 return NULL; 970 971 /* initiate the transfer */ 972 return _ftp_transfer(cd, CHECK_FLAG('a') ? "APPE" : "STOR", 973 url->doc, O_WRONLY, url->offset, flags); 974} 975 976/* 977 * Get file stats 978 */ 979int 980fetchStatFTP(struct url *url, struct url_stat *us, char *flags) 981{ 982 struct url *purl; 983 int cd; 984 985 /* get the proxy URL, and check if we should use HTTP instead */ 986 if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 987 if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) { 988 FILE *f; 989 990 if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL) 991 return -1; 992 fclose(f); 993 return 0; 994 } 995 } else { 996 purl = NULL; 997 } 998 999 /* connect to server */ 1000 cd = _ftp_cached_connect(url, purl, flags); 1001 if (purl) 1002 fetchFreeURL(purl); 1003 if (cd == NULL) 1004 return NULL; 1005 1006 /* change directory */ 1007 if (_ftp_cwd(cd, url->doc) == -1) 1008 return -1; 1009 1010 /* stat file */ 1011 return _ftp_stat(cd, url->doc, us); 1012} 1013 1014/* 1015 * List a directory 1016 */ 1017extern void warnx(char *, ...); 1018struct url_ent * 1019fetchListFTP(struct url *url, char *flags) 1020{ 1021 warnx("fetchListFTP(): not implemented"); 1022 return NULL; 1023} 1024