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