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