ftp.c revision 60383
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 * $FreeBSD: head/lib/libfetch/ftp.c 60383 2000-05-11 16:01:03Z des $
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 <sys/uio.h>
61#include <netinet/in.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 <time.h>
71#include <unistd.h>
72
73#include "fetch.h"
74#include "common.h"
75#include "ftperr.h"
76
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_FILE_STATUS			213
84#define FTP_SERVICE_READY		220
85#define FTP_PASSIVE_MODE		227
86#define FTP_LOGGED_IN			230
87#define FTP_FILE_ACTION_OK		250
88#define FTP_NEED_PASSWORD		331
89#define FTP_NEED_ACCOUNT		332
90#define FTP_FILE_OK			350
91#define FTP_SYNTAX_ERROR		500
92
93static char ENDL[2] = "\r\n";
94
95static struct url cached_host;
96static int cached_socket;
97
98static char *last_reply;
99static size_t lr_size, lr_length;
100static int last_code;
101
102#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
103			 && isdigit(foo[2]) && foo[3] == ' ')
104#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
105			&& isdigit(foo[2]) && foo[3] == '-')
106
107/*
108 * Get server response
109 */
110static int
111_ftp_chkerr(int cd)
112{
113    do {
114	if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) {
115	    _fetch_syserr();
116	    return -1;
117	}
118#ifndef NDEBUG
119	_fetch_info("got reply '%.*s'", lr_length - 2, last_reply);
120#endif
121    } while (isftpinfo(last_reply));
122
123    while (lr_length && isspace(last_reply[lr_length-1]))
124	lr_length--;
125    last_reply[lr_length] = 0;
126
127    if (!isftpreply(last_reply)) {
128	_ftp_seterr(999);
129	return -1;
130    }
131
132    last_code = (last_reply[0] - '0') * 100
133	+ (last_reply[1] - '0') * 10
134	+ (last_reply[2] - '0');
135
136    return last_code;
137}
138
139/*
140 * Send a command and check reply
141 */
142static int
143_ftp_cmd(int cd, char *fmt, ...)
144{
145    va_list ap;
146    struct iovec iov[2];
147    char *msg;
148    int r;
149
150    va_start(ap, fmt);
151    vasprintf(&msg, fmt, ap);
152    va_end(ap);
153
154    if (msg == NULL) {
155	errno = ENOMEM;
156	_fetch_syserr();
157	return -1;
158    }
159#ifndef NDEBUG
160    _fetch_info("sending '%s'", msg);
161#endif
162    iov[0].iov_base = msg;
163    iov[0].iov_len = strlen(msg);
164    iov[1].iov_base = ENDL;
165    iov[1].iov_len = sizeof ENDL;
166    r = writev(cd, iov, 2);
167    free(msg);
168    if (r == -1) {
169	_fetch_syserr();
170	return -1;
171    }
172
173    return _ftp_chkerr(cd);
174}
175
176/*
177 * Transfer file
178 */
179static FILE *
180_ftp_transfer(int cd, char *oper, char *file,
181	      char *mode, off_t offset, char *flags)
182{
183    struct sockaddr_in sin;
184    int pasv, high, verbose;
185    int e, sd = -1;
186    socklen_t l;
187    char *s;
188    FILE *df;
189
190    /* check flags */
191    pasv = (flags && strchr(flags, 'p'));
192    high = (flags && strchr(flags, 'h'));
193    verbose = (flags && strchr(flags, 'v'));
194
195    /* change directory */
196    if (((s = strrchr(file, '/')) != NULL) && (s != file)) {
197	*s = 0;
198	if (verbose)
199	    _fetch_info("changing directory to %s", file);
200	if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) {
201	    *s = '/';
202	    if (e != -1)
203		_ftp_seterr(e);
204	    return NULL;
205	}
206	*s++ = '/';
207    } else {
208	if (verbose)
209	    _fetch_info("changing directory to /");
210	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) {
211	    if (e != -1)
212		_ftp_seterr(e);
213	    return NULL;
214	}
215    }
216
217    /* s now points to file name */
218
219    /* open data socket */
220    if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
221	_fetch_syserr();
222	return NULL;
223    }
224
225    if (pasv) {
226	u_char addr[6];
227	char *ln, *p;
228	int i;
229
230	/* send PASV command */
231	if (verbose)
232	    _fetch_info("setting passive mode");
233	if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE)
234	    goto ouch;
235
236	/*
237	 * Find address and port number. The reply to the PASV command
238         * is IMHO the one and only weak point in the FTP protocol.
239	 */
240	ln = last_reply;
241	for (p = ln + 3; !isdigit(*p); p++)
242	    /* nothing */ ;
243	for (p--, i = 0; i < 6; i++) {
244	    p++; /* skip the comma */
245	    addr[i] = strtol(p, &p, 10);
246	}
247
248	/* seek to required offset */
249	if (offset)
250	    if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK)
251		goto sysouch;
252
253	/* construct sockaddr for data socket */
254	l = sizeof sin;
255	if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1)
256	    goto sysouch;
257	bcopy(addr, (char *)&sin.sin_addr, 4);
258	bcopy(addr + 4, (char *)&sin.sin_port, 2);
259
260	/* connect to data port */
261	if (verbose)
262	    _fetch_info("opening data connection");
263	if (connect(sd, (struct sockaddr *)&sin, sizeof sin) == -1)
264	    goto sysouch;
265
266	/* make the server initiate the transfer */
267	if (verbose)
268	    _fetch_info("initiating transfer");
269	e = _ftp_cmd(cd, "%s %s", oper, s);
270	if (e != FTP_OPEN_DATA_CONNECTION)
271	    goto ouch;
272
273    } else {
274	u_int32_t a;
275	u_short p;
276	int arg, d;
277
278	/* find our own address, bind, and listen */
279	l = sizeof sin;
280	if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1)
281	    goto sysouch;
282	sin.sin_port = 0;
283	arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT;
284	if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE,
285		       (char *)&arg, sizeof arg) == -1)
286	    goto sysouch;
287	if (verbose)
288	    _fetch_info("binding data socket");
289	if (bind(sd, (struct sockaddr *)&sin, l) == -1)
290	    goto sysouch;
291	if (listen(sd, 1) == -1)
292	    goto sysouch;
293
294	/* find what port we're on and tell the server */
295	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
296	    goto sysouch;
297	a = ntohl(sin.sin_addr.s_addr);
298	p = ntohs(sin.sin_port);
299	e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d",
300		     (a >> 24) & 0xff, (a >> 16) & 0xff,
301		     (a >> 8) & 0xff, a & 0xff,
302		     (p >> 8) & 0xff, p & 0xff);
303	if (e != FTP_OK)
304	    goto ouch;
305
306	/* make the server initiate the transfer */
307	if (verbose)
308	    _fetch_info("initiating transfer");
309	e = _ftp_cmd(cd, "%s %s", oper, s);
310	if (e != FTP_OPEN_DATA_CONNECTION)
311	    goto ouch;
312
313	/* accept the incoming connection and go to town */
314	if ((d = accept(sd, NULL, NULL)) == -1)
315	    goto sysouch;
316	close(sd);
317	sd = d;
318    }
319
320    if ((df = fdopen(sd, mode)) == NULL)
321	goto sysouch;
322    return df;
323
324sysouch:
325    _fetch_syserr();
326    close(sd);
327    return NULL;
328
329ouch:
330    if (e != -1)
331	_ftp_seterr(e);
332    close(sd);
333    return NULL;
334}
335
336/*
337 * Log on to FTP server
338 */
339static int
340_ftp_connect(char *host, int port, char *user, char *pwd, char *flags)
341{
342    int cd, e, pp = 0, direct, verbose;
343    char *p, *q;
344
345    direct = (flags && strchr(flags, 'd'));
346    verbose = (flags && strchr(flags, 'v'));
347
348    /* check for proxy */
349    if (!direct && (p = getenv("FTP_PROXY")) != NULL) {
350	if ((q = strchr(p, ':')) != NULL) {
351	    if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) {
352		/* XXX we should emit some kind of warning */
353	    }
354	    pp = atoi(q+1);
355	    if (pp < 1 || pp > 65535) {
356		/* XXX we should emit some kind of warning */
357	    }
358	}
359	if (!pp) {
360	    struct servent *se;
361
362	    if ((se = getservbyname("ftp", "tcp")) != NULL)
363		pp = ntohs(se->s_port);
364	    else
365		pp = FTP_DEFAULT_PORT;
366	}
367	if (q)
368	    *q = 0;
369	cd = _fetch_connect(p, pp, verbose);
370	if (q)
371	    *q = ':';
372    } else {
373	/* no proxy, go straight to target */
374	cd = _fetch_connect(host, port, verbose);
375	p = NULL;
376    }
377
378    /* check connection */
379    if (cd == -1) {
380	_fetch_syserr();
381	return NULL;
382    }
383
384    /* expect welcome message */
385    if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY)
386	goto fouch;
387
388    /* send user name and password */
389    if (!user || !*user)
390	user = FTP_ANONYMOUS_USER;
391    e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port)
392	  : _ftp_cmd(cd, "USER %s", user);
393
394    /* did the server request a password? */
395    if (e == FTP_NEED_PASSWORD) {
396	if (!pwd || !*pwd)
397	    pwd = FTP_ANONYMOUS_PASSWORD;
398	e = _ftp_cmd(cd, "PASS %s", pwd);
399    }
400
401    /* did the server request an account? */
402    if (e == FTP_NEED_ACCOUNT)
403	goto fouch;
404
405    /* we should be done by now */
406    if (e != FTP_LOGGED_IN)
407	goto fouch;
408
409    /* might as well select mode and type at once */
410#ifdef FTP_FORCE_STREAM_MODE
411    if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */
412	goto fouch;
413#endif
414    if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */
415	goto fouch;
416
417    /* done */
418    return cd;
419
420fouch:
421    if (e != -1)
422	_ftp_seterr(e);
423    close(cd);
424    return NULL;
425}
426
427/*
428 * Disconnect from server
429 */
430static void
431_ftp_disconnect(int cd)
432{
433    (void)_ftp_cmd(cd, "QUIT");
434    close(cd);
435}
436
437/*
438 * Check if we're already connected
439 */
440static int
441_ftp_isconnected(struct url *url)
442{
443    return (cached_socket
444	    && (strcmp(url->host, cached_host.host) == 0)
445	    && (strcmp(url->user, cached_host.user) == 0)
446	    && (strcmp(url->pwd, cached_host.pwd) == 0)
447	    && (url->port == cached_host.port));
448}
449
450/*
451 * Check the cache, reconnect if no luck
452 */
453static int
454_ftp_cached_connect(struct url *url, char *flags)
455{
456    int e, cd;
457
458    cd = -1;
459
460    /* set default port */
461    if (!url->port) {
462	struct servent *se;
463
464	if ((se = getservbyname("ftp", "tcp")) != NULL)
465	    url->port = ntohs(se->s_port);
466	else
467	    url->port = FTP_DEFAULT_PORT;
468    }
469
470    /* try to use previously cached connection */
471    if (_ftp_isconnected(url)) {
472	e = _ftp_cmd(cached_socket, "NOOP");
473	if (e == FTP_OK || e == FTP_SYNTAX_ERROR)
474	    cd = cached_socket;
475    }
476
477    /* connect to server */
478    if (cd == -1) {
479	cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags);
480	if (cd == -1)
481	    return -1;
482	if (cached_socket)
483	    _ftp_disconnect(cached_socket);
484	cached_socket = cd;
485	memcpy(&cached_host, url, sizeof *url);
486    }
487
488    return cd;
489}
490
491/*
492 * Get file
493 */
494FILE *
495fetchGetFTP(struct url *url, char *flags)
496{
497    int cd;
498
499    /* connect to server */
500    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
501	return NULL;
502
503    /* initiate the transfer */
504    return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags);
505}
506
507/*
508 * Put file
509 */
510FILE *
511fetchPutFTP(struct url *url, char *flags)
512{
513    int cd;
514
515    /* connect to server */
516    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
517	return NULL;
518
519    /* initiate the transfer */
520    return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR",
521			 url->doc, "w", url->offset, flags);
522}
523
524/*
525 * Get file stats
526 */
527int
528fetchStatFTP(struct url *url, struct url_stat *us, char *flags)
529{
530    char *ln, *s;
531    struct tm tm;
532    time_t t;
533    int e, cd;
534
535    /* connect to server */
536    if ((cd = _ftp_cached_connect(url, flags)) == NULL)
537	return -1;
538
539    /* change directory */
540    if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) {
541	*s = 0;
542	if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) {
543	    *s = '/';
544	    goto ouch;
545	}
546	*s++ = '/';
547    } else {
548	if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK)
549	    goto ouch;
550    }
551
552    /* s now points to file name */
553
554    if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS)
555	goto ouch;
556    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
557	/* nothing */ ;
558    for (us->size = 0; *ln && isdigit(*ln); ln++)
559	us->size = us->size * 10 + *ln - '0';
560    if (*ln && !isspace(*ln)) {
561	_ftp_seterr(999);
562	return -1;
563    }
564    DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size));
565
566    if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS)
567	goto ouch;
568    for (ln = last_reply + 4; *ln && isspace(*ln); ln++)
569	/* nothing */ ;
570    e = 999;
571    switch (strspn(ln, "0123456789")) {
572    case 14:
573	break;
574    case 15:
575	ln++;
576	ln[0] = '2';
577	ln[1] = '0';
578	break;
579    default:
580	goto ouch;
581    }
582    if (sscanf(ln, "%04d%02d%02d%02d%02d%02d",
583	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
584	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6)
585	goto ouch;
586    tm.tm_mon--;
587    tm.tm_year -= 1900;
588    tm.tm_isdst = -1;
589    t = timegm(&tm);
590    if (t == (time_t)-1)
591	t = time(NULL);
592    us->mtime = t;
593    us->atime = t;
594    DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
595		  "%02d:%02d:%02d\033[m]\n",
596		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
597		  tm.tm_hour, tm.tm_min, tm.tm_sec));
598    return 0;
599
600ouch:
601    if (e != -1)
602	_ftp_seterr(e);
603    return -1;
604}
605
606/*
607 * List a directory
608 */
609extern void warnx(char *, ...);
610struct url_ent *
611fetchListFTP(struct url *url, char *flags)
612{
613    warnx("fetchListFTP(): not implemented");
614    return NULL;
615}
616