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