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 * $Id: ftp.c,v 1.1.1.1 1998/07/09 16:52:42 des Exp $ 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/types.h> 59 #include <sys/socket.h> 60 #include <netinet/in.h> 61 #include <sys/errno.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 <unistd.h> 71 72 #include "fetch.h" 73 #include "ftperr.c" 74 75 #define FTP_DEFAULT_TO_ANONYMOUS 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_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 url_t cached_host; 91 static FILE *cached_socket; 92 93 static char *_ftp_last_reply; 94 95 /* 96 * Map error code to string 97 */ 98 static const char * 99 _ftp_errstring(int e) 100 { 101 struct ftperr *p = _ftp_errlist; 102 103 while ((p->num != -1) && (p->num != e)) 104 p++; 105 106 return p->string; 107 } 108 109 /* 110 * Set error code 111 */ 112 static void 113 _ftp_seterr(int e) 114 { 115 fetchLastErrCode = e; 116 fetchLastErrText = _ftp_errstring(e); 117 } 118 119 /* 120 * Set error code according to errno 121 */ 122 static void 123 _ftp_syserr(void) 124 { 125 fetchLastErrCode = errno; 126 fetchLastErrText = strerror(errno); 127 } 128 129 /* 130 * Get server response, check that first digit is a '2' 131 */ 132 static int 133 _ftp_chkerr(FILE *s, int *e) 134 { 135 char *line; 136 size_t len; 137 138 if (e) 139 *e = 0; 140 141 do { 142 if (((line = fgetln(s, &len)) == NULL) || (len < 4)) { 143 _ftp_syserr(); 144 return -1; 145 } 146 } while (line[3] == '-'); 147 148 _ftp_last_reply = line; 149 150 #ifndef NDEBUG 151 fprintf(stderr, "\033[1m<<< "); 152 fprintf(stderr, "%*.*s", (int)len, (int)len, line); 153 fprintf(stderr, "\033[m"); 154 #endif 155 156 if (!isdigit(line[1]) || !isdigit(line[1]) 157 || !isdigit(line[2]) || (line[3] != ' ')) { 158 _ftp_seterr(-1); 159 return -1; 160 } 161 162 _ftp_seterr((line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0')); 163 164 if (e) 165 *e = fetchLastErrCode; 166 167 return (line[0] == '2') - 1; 168 } 169 170 /* 171 * Send a command and check reply 172 */ 173 static int 174 _ftp_cmd(FILE *f, char *fmt, ...) 175 { 176 va_list ap; 177 int e; 178 179 va_start(ap, fmt); 180 vfprintf(f, fmt, ap); 181 #ifndef NDEBUG 182 fprintf(stderr, "\033[1m>>> "); 183 vfprintf(stderr, fmt, ap); 184 fprintf(stderr, "\033[m"); 185 #endif 186 va_end(ap); 187 188 _ftp_chkerr(f, &e); 189 return e; 190 } 191 192 /* 193 * Retrieve file 194 */ 195 static FILE * 196 _ftp_retrieve(FILE *cf, char *file, int pasv) 197 { 198 struct sockaddr_in sin; 199 int sd = -1, l; 200 char *s; 201 FILE *df; 202 203 /* change directory */ 204 if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 205 *s = 0; 206 if (_ftp_cmd(cf, "CWD %s" ENDL, file) != FTP_FILE_ACTION_OK) { 207 *s = '/'; 208 return NULL; 209 } 210 *s++ = '/'; 211 } else { 212 if (_ftp_cmd(cf, "CWD /" ENDL) != FTP_FILE_ACTION_OK) 213 return NULL; 214 } 215 216 /* s now points to file name */ 217 218 /* open data socket */ 219 if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { 220 _ftp_syserr(); 221 return NULL; 222 } 223 224 if (pasv) { 225 u_char addr[6]; 226 char *ln, *p; 227 int i; 228 229 /* send PASV command */ 230 if (_ftp_cmd(cf, "PASV" ENDL) != FTP_PASSIVE_MODE) 231 goto ouch; 232 233 /* find address and port number. The reply to the PASV command 234 is IMHO the one and only weak point in the FTP protocol. */ 235 ln = _ftp_last_reply; 236 for (p = ln + 3; !isdigit(*p); p++) 237 /* nothing */ ; 238 for (p--, i = 0; i < 6; i++) { 239 p++; /* skip the comma */ 240 addr[i] = strtol(p, &p, 10); 241 } 242 243 /* construct sockaddr for data socket */ 244 l = sizeof(sin); 245 if (getpeername(fileno(cf), (struct sockaddr *)&sin, &l) < 0) 246 goto sysouch; 247 bcopy(addr, (char *)&sin.sin_addr, 4); 248 bcopy(addr + 4, (char *)&sin.sin_port, 2); 249 250 /* connect to data port */ 251 if (connect(sd, (struct sockaddr *)&sin, sizeof(sin)) < 0) 252 goto sysouch; 253 254 /* make the server initiate the transfer */ 255 if (_ftp_cmd(cf, "RETR %s" ENDL, s) != FTP_OPEN_DATA_CONNECTION) 256 goto ouch; 257 258 } else { 259 u_int32_t a; 260 u_short p; 261 int d; 262 263 /* find our own address, bind, and listen */ 264 l = sizeof(sin); 265 if (getsockname(fileno(cf), (struct sockaddr *)&sin, &l) < 0) 266 goto sysouch; 267 sin.sin_port = 0; 268 if (bind(sd, (struct sockaddr *)&sin, l) < 0) 269 goto sysouch; 270 if (listen(sd, 1) < 0) 271 goto sysouch; 272 273 /* find what port we're on and tell the server */ 274 if (getsockname(sd, (struct sockaddr *)&sin, &l) < 0) 275 goto sysouch; 276 a = ntohl(sin.sin_addr.s_addr); 277 p = ntohs(sin.sin_port); 278 if (_ftp_cmd(cf, "PORT %d,%d,%d,%d,%d,%d" ENDL, 279 (a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff, 280 (p >> 8) & 0xff, p & 0xff) != FTP_OK) 281 goto ouch; 282 283 /* make the server initiate the transfer */ 284 if (_ftp_cmd(cf, "RETR %s" ENDL, s) != FTP_OPEN_DATA_CONNECTION) 285 goto ouch; 286 287 /* accept the incoming connection and go to town */ 288 if ((d = accept(sd, NULL, NULL)) < 0) 289 goto sysouch; 290 close(sd); 291 sd = d; 292 } 293 294 if ((df = fdopen(sd, "r")) == NULL) 295 goto sysouch; 296 return df; 297 298 sysouch: 299 _ftp_syserr(); 300 ouch: 301 close(sd); 302 return NULL; 303 } 304 305 /* 306 * Store file 307 */ 308 static FILE * 309 _ftp_store(FILE *cf, char *file, int pasv) 310 { 311 fprintf(stderr, "_ftp_store: not implemented yet.\n"); 312 313 cf = cf; 314 file = file; 315 pasv = pasv; 316 return NULL; 317 } 318 319 /* 320 * Log on to FTP server 321 */ 322 static FILE * 323 _ftp_connect(char *host, int port, char *user, char *pwd) 324 { 325 int sd, e; 326 FILE *f; 327 328 /* establish control connection */ 329 if ((sd = fetchConnect(host, port)) < 0) { 330 _ftp_syserr(); 331 return NULL; 332 } 333 if ((f = fdopen(sd, "r+")) == NULL) { 334 _ftp_syserr(); 335 goto ouch; 336 } 337 338 /* expect welcome message */ 339 if (_ftp_chkerr(f, NULL) < 0) 340 goto fouch; 341 342 /* send user name and password */ 343 e = _ftp_cmd(f, "USER %s" ENDL, user); 344 if (e == FTP_NEED_PASSWORD) /* server requested a password */ 345 e = _ftp_cmd(f, "PASS %s" ENDL, pwd); 346 if (e == FTP_NEED_ACCOUNT) /* server requested an account */ 347 /* help! */ ; 348 if (e != FTP_LOGGED_IN) /* won't let us near the WaReZ */ 349 goto fouch; 350 351 /* might as well select mode and type at once */ 352 #ifdef FTP_FORCE_STREAM_MODE 353 if (_ftp_cmd(f, "MODE S" ENDL) != FTP_OK) 354 goto ouch; 355 #endif 356 if (_ftp_cmd(f, "TYPE I" ENDL) != FTP_OK) 357 goto ouch; 358 359 /* done */ 360 return f; 361 362 ouch: 363 close(sd); 364 return NULL; 365 fouch: 366 fclose(f); 367 return NULL; 368 } 369 370 /* 371 * Disconnect from server 372 */ 373 static void 374 _ftp_disconnect(FILE *f) 375 { 376 _ftp_cmd(f, "QUIT" ENDL); 377 fclose(f); 378 } 379 380 /* 381 * Check if we're already connected 382 */ 383 static int 384 _ftp_isconnected(url_t *url) 385 { 386 return (cached_socket 387 && (strcmp(url->host, cached_host.host) == 0) 388 && (strcmp(url->user, cached_host.user) == 0) 389 && (strcmp(url->pwd, cached_host.pwd) == 0) 390 && (url->port == cached_host.port)); 391 } 392 393 FILE * 394 fetchGetFTP(url_t *url, char *flags) 395 { 396 FILE *cf = NULL; 397 int e; 398 399 #ifdef DEFAULT_TO_ANONYMOUS 400 if (!url->user[0]) { 401 strcpy(url->user, FTP_ANONYMOUS_USER); 402 strcpy(url->pwd, FTP_ANONYMOUS_PASSWORD); 403 } 404 #endif 405 406 /* set default port */ 407 if (!url->port) 408 url->port = FTP_DEFAULT_PORT; 409 410 /* try to use previously cached connection */ 411 if (_ftp_isconnected(url)) { 412 fprintf(cached_socket, "PWD" ENDL); 413 _ftp_chkerr(cached_socket, &e); 414 if (e > 0) 415 cf = cached_socket; 416 } 417 418 /* connect to server */ 419 if (!cf) { 420 cf = _ftp_connect(url->host, url->port, url->user, url->pwd); 421 if (!cf) 422 return NULL; 423 if (cached_socket) 424 _ftp_disconnect(cached_socket); 425 cached_socket = cf; 426 memcpy(&cached_host, url, sizeof(url_t)); 427 } 428 429 /* initiate the transfer */ 430 return _ftp_retrieve(cf, url->doc, (flags && strchr(flags, 'p'))); 431 } 432 433 /* 434 * Upload a file. 435 * Hmmm, that's almost an exact duplicate of the above... 436 */ 437 FILE * 438 fetchPutFTP(url_t *url, char *flags) 439 { 440 FILE *cf = NULL; 441 int e; 442 443 #ifdef DEFAULT_TO_ANONYMOUS 444 if (!url->user[0]) { 445 strcpy(url->user, FTP_ANONYMOUS_USER); 446 strcpy(url->pwd, FTP_ANONYMOUS_PASSWORD); 447 } 448 #endif 449 450 /* set default port */ 451 if (!url->port) 452 url->port = htons(FTP_DEFAULT_PORT); 453 454 /* try to use previously cached connection */ 455 if (_ftp_isconnected(url)) { 456 fprintf(cached_socket, "PWD" ENDL); 457 _ftp_chkerr(cached_socket, &e); 458 if (e > 0) 459 cf = cached_socket; 460 } 461 462 /* connect to server */ 463 if (!cf) { 464 cf = _ftp_connect(url->host, url->port, url->user, url->pwd); 465 if (!cf) 466 return NULL; 467 if (cached_socket) 468 _ftp_disconnect(cached_socket); 469 cached_socket = cf; 470 memcpy(&cached_host, url, sizeof(url_t)); 471 } 472 473 474 /* initiate the transfer */ 475 return _ftp_store(cf, url->doc, (flags && strchr(flags, 'p'))); 476 } 477