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