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