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