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