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 <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_LOGGED_IN 230 86 #define FTP_FILE_ACTION_OK 250 87 #define FTP_NEED_PASSWORD 331 88 #define FTP_NEED_ACCOUNT 332 89 #define FTP_SYNTAX_ERROR 500 90 91 static char ENDL[2] = "\r\n"; 92 93 static struct url cached_host; 94 static int cached_socket; 95 96 static char *last_reply; 97 static size_t lr_size, lr_length; 98 static int last_code; 99 100 #define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 101 && isdigit(foo[2]) && foo[3] == ' ') 102 #define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 103 && isdigit(foo[2]) && foo[3] == '-') 104 105 /* 106 * Get server response 107 */ 108 static int 109 _ftp_chkerr(int cd) 110 { 111 do { 112 if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 113 _fetch_syserr(); 114 return -1; 115 } 116 #ifndef NDEBUG 117 _fetch_info("got reply '%.*s'", lr_length - 2, last_reply); 118 #endif 119 } while (isftpinfo(last_reply)); 120 121 while (lr_length && isspace(last_reply[lr_length-1])) 122 lr_length--; 123 last_reply[lr_length] = 0; 124 125 if (!isftpreply(last_reply)) { 126 _ftp_seterr(999); 127 return -1; 128 } 129 130 last_code = (last_reply[0] - '0') * 100 131 + (last_reply[1] - '0') * 10 132 + (last_reply[2] - '0'); 133 134 return last_code; 135 } 136 137 /* 138 * Send a command and check reply 139 */ 140 static int 141 _ftp_cmd(int cd, char *fmt, ...) 142 { 143 va_list ap; 144 struct iovec iov[2]; 145 char *msg; 146 int r; 147 148 va_start(ap, fmt); 149 vasprintf(&msg, fmt, ap); 150 va_end(ap); 151 152 if (msg == NULL) { 153 errno = ENOMEM; 154 _fetch_syserr(); 155 return -1; 156 } 157 #ifndef NDEBUG 158 _fetch_info("sending '%s'", msg); 159 #endif 160 iov[0].iov_base = msg; 161 iov[0].iov_len = strlen(msg); 162 iov[1].iov_base = ENDL; 163 iov[1].iov_len = sizeof(ENDL); 164 r = writev(cd, iov, 2); 165 free(msg); 166 if (r == -1) { 167 _fetch_syserr(); 168 return -1; 169 } 170 171 return _ftp_chkerr(cd); 172 } 173 174 /* 175 * Transfer file 176 */ 177 static FILE * 178 _ftp_transfer(int cd, char *oper, char *file, char *mode, char *flags) 179 { 180 struct sockaddr_in sin; 181 int pasv, high, verbose; 182 int e, sd = -1; 183 socklen_t l; 184 char *s; 185 FILE *df; 186 187 /* check flags */ 188 pasv = (flags && strchr(flags, 'p')); 189 high = (flags && strchr(flags, 'h')); 190 verbose = (flags && strchr(flags, 'v')); 191 192 /* change directory */ 193 if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 194 *s = 0; 195 if (verbose) 196 _fetch_info("changing directory to %s", file); 197 if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) { 198 *s = '/'; 199 if (e != -1) 200 _ftp_seterr(e); 201 return NULL; 202 } 203 *s++ = '/'; 204 } else { 205 if (verbose) 206 _fetch_info("changing directory to /"); 207 if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) { 208 if (e != -1) 209 _ftp_seterr(e); 210 return NULL; 211 } 212 } 213 214 /* s now points to file name */ 215 216 /* open data socket */ 217 if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { 218 _fetch_syserr(); 219 return NULL; 220 } 221 222 if (pasv) { 223 u_char addr[6]; 224 char *ln, *p; 225 int i; 226 227 /* send PASV command */ 228 if (verbose) 229 _fetch_info("setting passive mode"); 230 if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 231 goto ouch; 232 233 /* 234 * Find address and port number. The reply to the PASV command 235 * is IMHO the one and only weak point in the FTP protocol. 236 */ 237 ln = last_reply; 238 for (p = ln + 3; !isdigit(*p); p++) 239 /* nothing */ ; 240 for (p--, i = 0; i < 6; i++) { 241 p++; /* skip the comma */ 242 addr[i] = strtol(p, &p, 10); 243 } 244 245 /* construct sockaddr for data socket */ 246 l = sizeof(sin); 247 if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 248 goto sysouch; 249 bcopy(addr, (char *)&sin.sin_addr, 4); 250 bcopy(addr + 4, (char *)&sin.sin_port, 2); 251 252 /* connect to data port */ 253 if (verbose) 254 _fetch_info("opening data connection"); 255 if (connect(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) 256 goto sysouch; 257 258 /* make the server initiate the transfer */ 259 if (verbose) 260 _fetch_info("initiating transfer"); 261 e = _ftp_cmd(cd, "%s %s", oper, s); 262 if (e != FTP_OPEN_DATA_CONNECTION) 263 goto ouch; 264 265 } else { 266 u_int32_t a; 267 u_short p; 268 int arg, d; 269 270 /* find our own address, bind, and listen */ 271 l = sizeof(sin); 272 if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 273 goto sysouch; 274 sin.sin_port = 0; 275 arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; 276 if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 277 (char *)&arg, sizeof(arg)) == -1) 278 goto sysouch; 279 if (verbose) 280 _fetch_info("binding data socket"); 281 if (bind(sd, (struct sockaddr *)&sin, l) == -1) 282 goto sysouch; 283 if (listen(sd, 1) == -1) 284 goto sysouch; 285 286 /* find what port we're on and tell the server */ 287 if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 288 goto sysouch; 289 a = ntohl(sin.sin_addr.s_addr); 290 p = ntohs(sin.sin_port); 291 e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 292 (a >> 24) & 0xff, (a >> 16) & 0xff, 293 (a >> 8) & 0xff, a & 0xff, 294 (p >> 8) & 0xff, p & 0xff); 295 if (e != FTP_OK) 296 goto ouch; 297 298 /* make the server initiate the transfer */ 299 if (verbose) 300 _fetch_info("initiating transfer"); 301 e = _ftp_cmd(cd, "%s %s", oper, s); 302 if (e != FTP_OPEN_DATA_CONNECTION) 303 goto ouch; 304 305 /* accept the incoming connection and go to town */ 306 if ((d = accept(sd, NULL, NULL)) == -1) 307 goto sysouch; 308 close(sd); 309 sd = d; 310 } 311 312 if ((df = fdopen(sd, mode)) == NULL) 313 goto sysouch; 314 return df; 315 316 sysouch: 317 _fetch_syserr(); 318 close(sd); 319 return NULL; 320 321 ouch: 322 if (e != -1) 323 _ftp_seterr(e); 324 close(sd); 325 return NULL; 326 } 327 328 /* 329 * Log on to FTP server 330 */ 331 static int 332 _ftp_connect(char *host, int port, char *user, char *pwd, char *flags) 333 { 334 int cd, e, pp = FTP_DEFAULT_PORT, direct, verbose; 335 char *p, *q; 336 337 direct = (flags && strchr(flags, 'd')); 338 verbose = (flags && strchr(flags, 'v')); 339 340 /* check for proxy */ 341 if (!direct && (p = getenv("FTP_PROXY")) != NULL) { 342 if ((q = strchr(p, ':')) != NULL) { 343 /* XXX check that it's a valid number */ 344 pp = atoi(q+1); 345 } 346 if (q) 347 *q = 0; 348 cd = _fetch_connect(p, pp, verbose); 349 if (q) 350 *q = ':'; 351 } else { 352 /* no proxy, go straight to target */ 353 cd = _fetch_connect(host, port, verbose); 354 p = NULL; 355 } 356 357 /* check connection */ 358 if (cd == -1) { 359 _fetch_syserr(); 360 return NULL; 361 } 362 363 /* expect welcome message */ 364 if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 365 goto fouch; 366 367 /* send user name and password */ 368 if (!user || !*user) 369 user = FTP_ANONYMOUS_USER; 370 e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port) 371 : _ftp_cmd(cd, "USER %s", user); 372 373 /* did the server request a password? */ 374 if (e == FTP_NEED_PASSWORD) { 375 if (!pwd || !*pwd) 376 pwd = FTP_ANONYMOUS_PASSWORD; 377 e = _ftp_cmd(cd, "PASS %s", pwd); 378 } 379 380 /* did the server request an account? */ 381 if (e == FTP_NEED_ACCOUNT) 382 goto fouch; 383 384 /* we should be done by now */ 385 if (e != FTP_LOGGED_IN) 386 goto fouch; 387 388 /* might as well select mode and type at once */ 389 #ifdef FTP_FORCE_STREAM_MODE 390 if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 391 goto fouch; 392 #endif 393 if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 394 goto fouch; 395 396 /* done */ 397 return cd; 398 399 fouch: 400 if (e != -1) 401 _ftp_seterr(e); 402 close(cd); 403 return NULL; 404 } 405 406 /* 407 * Disconnect from server 408 */ 409 static void 410 _ftp_disconnect(int cd) 411 { 412 (void)_ftp_cmd(cd, "QUIT"); 413 close(cd); 414 } 415 416 /* 417 * Check if we're already connected 418 */ 419 static int 420 _ftp_isconnected(struct url *url) 421 { 422 return (cached_socket 423 && (strcmp(url->host, cached_host.host) == 0) 424 && (strcmp(url->user, cached_host.user) == 0) 425 && (strcmp(url->pwd, cached_host.pwd) == 0) 426 && (url->port == cached_host.port)); 427 } 428 429 /* 430 * Check the cache, reconnect if no luck 431 */ 432 static int 433 _ftp_cached_connect(struct url *url, char *flags) 434 { 435 int e, cd; 436 437 cd = -1; 438 439 /* set default port */ 440 if (!url->port) 441 url->port = FTP_DEFAULT_PORT; 442 443 /* try to use previously cached connection */ 444 if (_ftp_isconnected(url)) { 445 e = _ftp_cmd(cached_socket, "NOOP"); 446 if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 447 cd = cached_socket; 448 } 449 450 /* connect to server */ 451 if (cd == -1) { 452 cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags); 453 if (cd == -1) 454 return -1; 455 if (cached_socket) 456 _ftp_disconnect(cached_socket); 457 cached_socket = cd; 458 memcpy(&cached_host, url, sizeof(struct url)); 459 } 460 461 return cd; 462 } 463 464 /* 465 * Get file 466 */ 467 FILE * 468 fetchGetFTP(struct url *url, char *flags) 469 { 470 int cd; 471 472 /* connect to server */ 473 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 474 return NULL; 475 476 /* initiate the transfer */ 477 return _ftp_transfer(cd, "RETR", url->doc, "r", flags); 478 } 479 480 /* 481 * Put file 482 */ 483 FILE * 484 fetchPutFTP(struct url *url, char *flags) 485 { 486 int cd; 487 488 /* connect to server */ 489 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 490 return NULL; 491 492 /* initiate the transfer */ 493 return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR", 494 url->doc, "w", flags); 495 } 496 497 /* 498 * Get file stats 499 */ 500 int 501 fetchStatFTP(struct url *url, struct url_stat *us, char *flags) 502 { 503 char *ln, *s; 504 struct tm tm; 505 time_t t; 506 int e, cd; 507 508 /* connect to server */ 509 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 510 return -1; 511 512 /* change directory */ 513 if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) { 514 *s = 0; 515 if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) { 516 *s = '/'; 517 goto ouch; 518 } 519 *s++ = '/'; 520 } else { 521 if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) 522 goto ouch; 523 } 524 525 /* s now points to file name */ 526 527 if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS) 528 goto ouch; 529 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 530 /* nothing */ ; 531 for (us->size = 0; *ln && isdigit(*ln); ln++) 532 us->size = us->size * 10 + *ln - '0'; 533 if (*ln && !isspace(*ln)) { 534 _ftp_seterr(999); 535 return -1; 536 } 537 538 if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) 539 goto ouch; 540 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 541 /* nothing */ ; 542 sscanf(ln, "%04d%02d%02d%02d%02d%02d", 543 &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 544 &tm.tm_hour, &tm.tm_min, &tm.tm_sec); 545 /* XXX should check the return value from sscanf */ 546 tm.tm_mon--; 547 tm.tm_year -= 1900; 548 tm.tm_isdst = -1; 549 t = mktime(&tm); 550 if (t == (time_t)-1) 551 t = time(NULL); 552 else 553 t += tm.tm_gmtoff; 554 us->mtime = t; 555 us->atime = t; 556 return 0; 557 558 ouch: 559 if (e != -1) 560 _ftp_seterr(e); 561 return -1; 562 } 563 564 /* 565 * List a directory 566 */ 567 extern void warnx(char *, ...); 568 struct url_ent * 569 fetchListFTP(struct url *url, char *flags) 570 { 571 warnx("fetchListFTP(): not implemented"); 572 return NULL; 573 } 574