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