xref: /freebsd/lib/libfetch/ftp.c (revision 346298f0de0b685c97cbbee3f6d109d53253c31f)
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 
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  * Retrieve file
194  */
195 static 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 
298 sysouch:
299     _ftp_syserr();
300 ouch:
301     close(sd);
302     return NULL;
303 }
304 
305 /*
306  * Store file
307  */
308 static 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  */
322 static 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 
362 ouch:
363     close(sd);
364     return NULL;
365 fouch:
366     fclose(f);
367     return NULL;
368 }
369 
370 /*
371  * Disconnect from server
372  */
373 static 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  */
383 static 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 
393 FILE *
394 fetchGetFTP(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  */
437 FILE *
438 fetchPutFTP(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