xref: /freebsd/lib/libfetch/ftp.c (revision a8445737e740901f5f2c8d24c12ef7fc8b00134e)
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.4 1998/07/12 22:34:39 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 
90 static url_t cached_host;
91 static FILE *cached_socket;
92 
93 static char *_ftp_last_reply;
94 
95 /*
96  * Map error code to string
97  */
98 static 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  */
112 static 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  */
122 static 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  */
132 static 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  */
173 static 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  * Transfer file
194  */
195 static FILE *
196 _ftp_transfer(FILE *cf, char *oper, char *file, char *mode, 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)) == -1) {
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) == -1)
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)) == -1)
252 	    goto sysouch;
253 
254 	/* make the server initiate the transfer */
255 	if (_ftp_cmd(cf, "%s %s" ENDL, oper, 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) == -1)
266 	    goto sysouch;
267 	sin.sin_port = 0;
268 	if (bind(sd, (struct sockaddr *)&sin, l) == -1)
269 	    goto sysouch;
270 	if (listen(sd, 1) == -1)
271 	    goto sysouch;
272 
273 	/* find what port we're on and tell the server */
274 	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
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, "%s %s" ENDL, oper, 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)) == -1)
289 	    goto sysouch;
290 	close(sd);
291 	sd = d;
292     }
293 
294     if ((df = fdopen(sd, mode)) == NULL)
295 	goto sysouch;
296     return df;
297 
298 sysouch:
299     _ftp_syserr();
300 ouch:
301     close(sd);
302     return NULL;
303 }
304 
305 /*
306  * Log on to FTP server
307  */
308 static FILE *
309 _ftp_connect(char *host, int port, char *user, char *pwd)
310 {
311     int sd, e, pp = FTP_DEFAULT_PORT;
312     char *p, *q;
313     FILE *f;
314 
315     /* check for proxy */
316     if ((p = getenv("FTP_PROXY")) != NULL) {
317 	if ((q = strchr(p, ':')) != NULL) {
318 	    /* XXX check that it's a valid number */
319 	    pp = atoi(q+1);
320 	}
321 	if (q)
322 	    *q = 0;
323 	sd = fetchConnect(p, pp);
324 	if (q)
325 	    *q = ':';
326     } else {
327 	/* no proxy, go straight to target */
328 	sd = fetchConnect(host, port);
329     }
330 
331     /* check connection */
332     if (sd == -1) {
333 	_ftp_syserr();
334 	return NULL;
335     }
336 
337     /* streams make life easier */
338     if ((f = fdopen(sd, "r+")) == NULL) {
339 	_ftp_syserr();
340 	goto ouch;
341     }
342 
343     /* expect welcome message */
344     if (_ftp_chkerr(f, NULL) == -1)
345 	goto fouch;
346 
347     /* send user name and password */
348     if (!user || !*user)
349 	user = FTP_ANONYMOUS_USER;
350     e = p ? _ftp_cmd(f, "USER %s@%s@%d" ENDL, user, host, port)
351 	  : _ftp_cmd(f, "USER %s" ENDL, user);
352 
353     /* did the server request a password? */
354     if (e == FTP_NEED_PASSWORD) {
355 	if (!pwd || !*pwd)
356 	    pwd = FTP_ANONYMOUS_PASSWORD;
357 	e = _ftp_cmd(f, "PASS %s" ENDL, pwd);
358     }
359 
360     /* did the server request an account? */
361     if (e == FTP_NEED_ACCOUNT)
362 	/* help! */ ;
363 
364     /* we should be done by now */
365     if (e != FTP_LOGGED_IN)
366 	goto fouch;
367 
368     /* might as well select mode and type at once */
369 #ifdef FTP_FORCE_STREAM_MODE
370     if (_ftp_cmd(f, "MODE S" ENDL) != FTP_OK) /* default is S */
371 	goto ouch;
372 #endif
373     if (_ftp_cmd(f, "TYPE I" ENDL) != FTP_OK) /* default is A */
374 	goto ouch;
375 
376     /* done */
377     return f;
378 
379 ouch:
380     close(sd);
381     return NULL;
382 fouch:
383     fclose(f);
384     return NULL;
385 }
386 
387 /*
388  * Disconnect from server
389  */
390 static void
391 _ftp_disconnect(FILE *f)
392 {
393     _ftp_cmd(f, "QUIT" ENDL);
394     fclose(f);
395 }
396 
397 /*
398  * Check if we're already connected
399  */
400 static int
401 _ftp_isconnected(url_t *url)
402 {
403     return (cached_socket
404 	    && (strcmp(url->host, cached_host.host) == 0)
405 	    && (strcmp(url->user, cached_host.user) == 0)
406 	    && (strcmp(url->pwd, cached_host.pwd) == 0)
407 	    && (url->port == cached_host.port));
408 }
409 
410 /*
411  * FTP session
412  */
413 static FILE *
414 fetchXxxFTP(url_t *url, char *oper, char *mode, char *flags)
415 {
416     FILE *cf = NULL;
417     int e;
418 
419     /* set default port */
420     if (!url->port)
421 	url->port = FTP_DEFAULT_PORT;
422 
423     /* try to use previously cached connection; there should be a 226 waiting */
424     if (_ftp_isconnected(url)) {
425 	_ftp_chkerr(cached_socket, &e);
426 	if (e > 0)
427 	    cf = cached_socket;
428     }
429 
430     /* connect to server */
431     if (!cf) {
432 	cf = _ftp_connect(url->host, url->port, url->user, url->pwd);
433 	if (!cf)
434 	    return NULL;
435 	if (cached_socket)
436 	    _ftp_disconnect(cached_socket);
437 	cached_socket = cf;
438 	memcpy(&cached_host, url, sizeof(url_t));
439     }
440 
441     /* initiate the transfer */
442     return _ftp_transfer(cf, oper, url->doc, mode, (flags && strchr(flags, 'p')));
443 }
444 
445 /*
446  * Itsy bitsy teeny weenie
447  */
448 FILE *
449 fetchGetFTP(url_t *url, char *flags)
450 {
451     return fetchXxxFTP(url, "RETR", "r", flags);
452 }
453 
454 FILE *
455 fetchPutFTP(url_t *url, char *flags)
456 {
457     if (flags && strchr(flags, 'a'))
458 	return fetchXxxFTP(url, "APPE", "w", flags);
459     else return fetchXxxFTP(url, "STOR", "w", flags);
460 }
461