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