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