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