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