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