ftp.c revision 37573
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
90static url_t cached_host;
91static FILE *cached_socket;
92
93static char *_ftp_last_reply;
94
95/*
96 * Map error code to string
97 */
98static 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 */
112static 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 */
122static 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 */
132static 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 */
173static 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 */
195static 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
298sysouch:
299    _ftp_syserr();
300ouch:
301    close(sd);
302    return NULL;
303}
304
305/*
306 * Store file
307 */
308static 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 */
322static 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
362ouch:
363    close(sd);
364    return NULL;
365fouch:
366    fclose(f);
367    return NULL;
368}
369
370/*
371 * Disconnect from server
372 */
373static 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 */
383static 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
393FILE *
394fetchGetFTP(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 */
437FILE *
438fetchPutFTP(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