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