xref: /freebsd/lib/libfetch/ftp.c (revision 8e3986ea3696cf3ec8fddbb5ceb8685642200d0e)
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 <stdio.h>
67 #include <stdlib.h>
68 #include <string.h>
69 #include <unistd.h>
70 
71 #include "fetch.h"
72 #include "ftperr.c"
73 
74 #define FTP_DEFAULT_TO_ANONYMOUS
75 #define FTP_ANONYMOUS_USER	"ftp"
76 #define FTP_ANONYMOUS_PASSWORD	"ftp"
77 
78 #define ENDL "\r\n"
79 
80 static url_t cached_host;
81 static FILE *cached_socket;
82 
83 #ifndef NDEBUG
84 #define TRACE fprintf(stderr, "TRACE on line %d in " __FILE__ "\n", __LINE__);
85 #else
86 #define TRACE
87 #endif
88 
89 /*
90  * Map error code to string
91  */
92 static const char *
93 _ftp_errstring(int e)
94 {
95     struct ftperr *p = _ftp_errlist;
96 
97     while ((p->num != -1) && (p->num != e))
98 	p++;
99 
100     return p->string;
101 }
102 
103 /*
104  * Set error code
105  */
106 static void
107 _ftp_seterr(int e)
108 {
109     fetchLastErrCode = e;
110     fetchLastErrText = _ftp_errstring(e);
111 }
112 
113 /*
114  * Set error code according to errno
115  */
116 static void
117 _ftp_syserr(void)
118 {
119     fetchLastErrCode = errno;
120     fetchLastErrText = strerror(errno);
121 }
122 
123 /*
124  * Get server response, check that first digit is a '2'
125  */
126 static int
127 _ftp_chkerr(FILE *s, int *e)
128 {
129     char *line;
130     size_t len;
131 
132     TRACE;
133 
134     if (e)
135 	*e = 0;
136 
137     do {
138 	if (((line = fgetln(s, &len)) == NULL) || (len < 4))
139 	{
140 	    _ftp_syserr();
141 	    return -1;
142 	}
143     } while (line[3] == '-');
144 
145     if (!isdigit(line[1]) || !isdigit(line[1])
146 	|| !isdigit(line[2]) || (line[3] != ' ')) {
147 	_ftp_seterr(-1);
148 	return -1;
149     }
150 
151     _ftp_seterr((line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0'));
152 
153     if (e)
154 	*e = fetchLastErrCode;
155 
156     return (line[0] == '2') - 1;
157 }
158 
159 /*
160  * Change remote working directory
161  */
162 static int
163 _ftp_cwd(FILE *s, char *dir)
164 {
165     TRACE;
166 
167     fprintf(s, "CWD %s\n", dir);
168     if (ferror(s)) {
169 	_ftp_syserr();
170 	return -1;
171     }
172     return _ftp_chkerr(s, NULL); /* expecting 250 */
173 }
174 
175 /*
176  * Retrieve file
177  */
178 static FILE *
179 _ftp_retrieve(FILE *cf, char *file, int pasv)
180 {
181     char *p;
182 
183     TRACE;
184 
185     /* change directory */
186     if (((p = strrchr(file, '/')) != NULL) && (p != file)) {
187 	*p = 0;
188 	if (_ftp_cwd(cf, file) < 0) {
189 	    *p = '/';
190 	    return NULL;
191 	}
192 	*p++ = '/';
193     } else {
194 	if (_ftp_cwd(cf, "/") < 0)
195 	    return NULL;
196     }
197 
198     /* retrieve file; p now points to file name */
199     fprintf(stderr, "Arrrgh! No! No! I can't do it! Leave me alone!\n");
200     return NULL;
201 }
202 
203 /*
204  * Store file
205  */
206 static FILE *
207 _ftp_store(FILE *cf, char *file, int pasv)
208 {
209     TRACE;
210 
211     cf = cf;
212     file = file;
213     pasv = pasv;
214     return NULL;
215 }
216 
217 /*
218  * Log on to FTP server
219  */
220 static FILE *
221 _ftp_connect(char *host, int port, char *user, char *pwd)
222 {
223     int sd, e;
224     FILE *f;
225 
226     TRACE;
227 
228     /* establish control connection */
229     if ((sd = fetchConnect(host, port)) < 0) {
230 	_ftp_syserr();
231 	return NULL;
232     }
233     if ((f = fdopen(sd, "r+")) == NULL) {
234 	_ftp_syserr();
235 	goto ouch;
236     }
237 
238     /* expect welcome message */
239     if (_ftp_chkerr(f, NULL) < 0)
240 	goto fouch;
241 
242     /* send user name and password */
243     fprintf(f, "USER %s" ENDL, user);
244     _ftp_chkerr(f, &e);
245     if (e == 331) {
246 	/* server requested a password */
247 	fprintf(f, "PASS %s" ENDL, pwd);
248 	_ftp_chkerr(f, &e);
249     }
250     if (e == 332) {
251 	/* server requested an account */
252     }
253     if (e != 230) /* won't let us near the WaReZ */
254 	goto fouch;
255 
256     /* might as well select mode and type at once */
257 #ifdef FTP_FORCE_STREAM_MODE
258     fprintf(f, "MODE S" ENDL);
259     if (_ftp_chkerr(f, NULL) < 0)
260 	goto ouch;
261 #endif
262     fprintf(f, "TYPE I" ENDL);
263     if (_ftp_chkerr(f, NULL) < 0)
264 	goto ouch;
265 
266     /* done */
267     return f;
268 
269 ouch:
270     close(sd);
271     return NULL;
272 fouch:
273     fclose(f);
274     return NULL;
275 }
276 
277 /*
278  * Disconnect from server
279  */
280 static void
281 _ftp_disconnect(FILE *f)
282 {
283     TRACE;
284 
285     fprintf(f, "QUIT" ENDL);
286     _ftp_chkerr(f, NULL);
287     fclose(f);
288 }
289 
290 /*
291  * Check if we're already connected
292  */
293 static int
294 _ftp_isconnected(url_t *url)
295 {
296     TRACE;
297 
298     return (cached_socket
299 	    && (strcmp(url->host, cached_host.host) == 0)
300 	    && (strcmp(url->user, cached_host.user) == 0)
301 	    && (strcmp(url->pwd, cached_host.pwd) == 0)
302 	    && (url->port == cached_host.port));
303 }
304 
305 FILE *
306 fetchGetFTP(url_t *url, char *flags)
307 {
308     FILE *cf = NULL;
309     int e;
310 
311     TRACE;
312 
313 #ifdef DEFAULT_TO_ANONYMOUS
314     if (!url->user[0]) {
315 	strcpy(url->user, FTP_ANONYMOUS_USER);
316 	strcpy(url->pwd, FTP_ANONYMOUS_PASSWORD);
317     }
318 #endif
319 
320     /* set default port */
321     if (!url->port)
322 	url->port = 21;
323 
324     /* try to use previously cached connection */
325     if (_ftp_isconnected(url)) {
326 	fprintf(cached_socket, "PWD" ENDL);
327 	_ftp_chkerr(cached_socket, &e);
328 	if (e > 0)
329 	    cf = cached_socket;
330     }
331 
332     /* connect to server */
333     if (!cf) {
334 	cf = _ftp_connect(url->host, url->port, url->user, url->pwd);
335 	if (!cf)
336 	    return NULL;
337 	if (cached_socket)
338 	    _ftp_disconnect(cached_socket);
339 	cached_socket = cf;
340 	memcpy(&cached_host, url, sizeof(url_t));
341     }
342 
343     /* initiate the transfer */
344     return _ftp_retrieve(cf, url->doc, (flags && strchr(flags, 'p')));
345 }
346 
347 /*
348  * Upload a file.
349  * Hmmm, that's almost an exact duplicate of the above...
350  */
351 FILE *
352 fetchPutFTP(url_t *url, char *flags)
353 {
354     FILE *cf = NULL;
355     int e;
356 
357 #ifdef DEFAULT_TO_ANONYMOUS
358     if (!url->user[0]) {
359 	strcpy(url->user, FTP_ANONYMOUS_USER);
360 	strcpy(url->pwd, FTP_ANONYMOUS_PASSWORD);
361     }
362 #endif
363 
364     /* set default port */
365     if (!url->port)
366 	url->port = 21;
367 
368     /* try to use previously cached connection */
369     if (_ftp_isconnected(url)) {
370 	fprintf(cached_socket, "PWD" ENDL);
371 	_ftp_chkerr(cached_socket, &e);
372 	if (e > 0)
373 	    cf = cached_socket;
374     }
375 
376     /* connect to server */
377     if (!cf) {
378 	cf = _ftp_connect(url->host, url->port, url->user, url->pwd);
379 	if (!cf)
380 	    return NULL;
381 	if (cached_socket)
382 	    _ftp_disconnect(cached_socket);
383 	cached_socket = cf;
384 	memcpy(&cached_host, url, sizeof(url_t));
385     }
386 
387 
388     /* initiate the transfer */
389     return _ftp_store(cf, url->doc, (flags && strchr(flags, 'p')));
390 }
391