ftp.c revision 41862
1226890Sdim/*-
2193326Sed * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
3193326Sed * All rights reserved.
4193326Sed *
5193326Sed * Redistribution and use in source and binary forms, with or without
6193326Sed * modification, are permitted provided that the following conditions
7193326Sed * are met:
8193326Sed * 1. Redistributions of source code must retain the above copyright
9193326Sed *    notice, this list of conditions and the following disclaimer
10193326Sed *    in this position and unchanged.
11226890Sdim * 2. Redistributions in binary form must reproduce the above copyright
12193326Sed *    notice, this list of conditions and the following disclaimer in the
13193326Sed *    documentation and/or other materials provided with the distribution.
14193326Sed * 3. The name of the author may not be used to endorse or promote products
15212904Sdim *    derived from this software without specific prior written permission
16212904Sdim *
17193326Sed * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18223017Sdim * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19252723Sdim * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20193326Sed * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21212904Sdim * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22193326Sed * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23193326Sed * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24193326Sed * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25193326Sed * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26193326Sed * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27193326Sed *
28193326Sed *	$Id: ftp.c,v 1.7 1998/11/06 22:14:08 des Exp $
29193326Sed */
30193326Sed
31193326Sed/*
32193326Sed * Portions of this code were taken from or based on ftpio.c:
33193326Sed *
34198092Srdivacky * ----------------------------------------------------------------------------
35193326Sed * "THE BEER-WARE LICENSE" (Revision 42):
36193326Sed * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
37193326Sed * can do whatever you want with this stuff. If we meet some day, and you think
38193326Sed * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
39193326Sed * ----------------------------------------------------------------------------
40193326Sed *
41193326Sed * Major Changelog:
42193326Sed *
43193326Sed * Dag-Erling Co�dan Sm�rgrav
44198092Srdivacky * 9 Jun 1998
45235633Sdim *
46208600Srdivacky * Incorporated into libfetch
47198092Srdivacky *
48235633Sdim * Jordan K. Hubbard
49208600Srdivacky * 17 Jan 1996
50208600Srdivacky *
51208600Srdivacky * Turned inside out. Now returns xfers as new file ids, not as a special
52208600Srdivacky * `state' of FTP_t
53193326Sed *
54193326Sed * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
55198092Srdivacky *
56208600Srdivacky */
57208600Srdivacky
58208600Srdivacky#include <sys/param.h>
59193326Sed#include <sys/socket.h>
60198092Srdivacky#include <netinet/in.h>
61226890Sdim#include <sys/errno.h>
62193326Sed
63226890Sdim#include <ctype.h>
64208600Srdivacky#include <errno.h>
65226890Sdim#include <netdb.h>
66226890Sdim#include <stdarg.h>
67193326Sed#include <stdio.h>
68193326Sed#include <stdlib.h>
69193326Sed#include <string.h>
70210299Sed#include <unistd.h>
71224145Sdim
72224145Sdim#include "fetch.h"
73224145Sdim#include "common.h"
74224145Sdim#include "ftperr.h"
75193326Sed
76208600Srdivacky#define FTP_DEFAULT_TO_ANONYMOUS
77235633Sdim#define FTP_ANONYMOUS_USER	"ftp"
78208600Srdivacky#define FTP_ANONYMOUS_PASSWORD	"ftp"
79218893Sdim#define FTP_DEFAULT_PORT 21
80226890Sdim
81235633Sdim#define FTP_OPEN_DATA_CONNECTION	150
82235633Sdim#define FTP_OK				200
83208600Srdivacky#define FTP_PASSIVE_MODE		227
84208600Srdivacky#define FTP_LOGGED_IN			230
85193326Sed#define FTP_FILE_ACTION_OK		250
86193326Sed#define FTP_NEED_PASSWORD		331
87193326Sed#define FTP_NEED_ACCOUNT		332
88193326Sed
89193326Sed#define ENDL "\r\n"
90193326Sed
91208600Srdivackystatic struct url cached_host;
92198092Srdivackystatic FILE *cached_socket;
93193326Sed
94193326Sedstatic char *_ftp_last_reply;
95224145Sdim
96224145Sdim/*
97198092Srdivacky * Get server response, check that first digit is a '2'
98193326Sed */
99193326Sedstatic int
100208600Srdivacky_ftp_chkerr(FILE *s, int *e)
101193326Sed{
102198092Srdivacky    char *line;
103208600Srdivacky    size_t len;
104208600Srdivacky    int err;
105208600Srdivacky
106208600Srdivacky    if (e)
107208600Srdivacky	*e = 0;
108208600Srdivacky
109208600Srdivacky    do {
110208600Srdivacky	if (((line = fgetln(s, &len)) == NULL) || (len < 4)) {
111208600Srdivacky	    _fetch_syserr();
112208600Srdivacky	    return -1;
113208600Srdivacky	}
114208600Srdivacky    } while (line[3] == '-');
115208600Srdivacky
116208600Srdivacky    _ftp_last_reply = line;
117208600Srdivacky
118208600Srdivacky#ifndef NDEBUG
119208600Srdivacky    fprintf(stderr, "\033[1m<<< ");
120224145Sdim    fprintf(stderr, "%*.*s", (int)len, (int)len, line);
121224145Sdim    fprintf(stderr, "\033[m");
122193326Sed#endif
123193326Sed
124224145Sdim    if (!isdigit(line[1]) || !isdigit(line[1])
125193326Sed	|| !isdigit(line[2]) || (line[3] != ' ')) {
126245431Sdim	_ftp_seterr(0);
127193326Sed	return -1;
128208600Srdivacky    }
129208600Srdivacky
130224145Sdim    err = (line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0');
131224145Sdim    _ftp_seterr(err);
132224145Sdim
133224145Sdim    if (e)
134224145Sdim	*e = err;
135224145Sdim
136224145Sdim    return (line[0] == '2') - 1;
137224145Sdim}
138235633Sdim
139224145Sdim/*
140224145Sdim * Send a command and check reply
141224145Sdim */
142224145Sdimstatic int
143224145Sdim_ftp_cmd(FILE *f, char *fmt, ...)
144224145Sdim{
145224145Sdim    va_list ap;
146224145Sdim    int e;
147224145Sdim
148224145Sdim    va_start(ap, fmt);
149224145Sdim    vfprintf(f, fmt, ap);
150224145Sdim#ifndef NDEBUG
151224145Sdim    fprintf(stderr, "\033[1m>>> ");
152235633Sdim    vfprintf(stderr, fmt, ap);
153235633Sdim    fprintf(stderr, "\033[m");
154224145Sdim#endif
155224145Sdim    va_end(ap);
156224145Sdim
157224145Sdim    _ftp_chkerr(f, &e);
158224145Sdim    return e;
159224145Sdim}
160224145Sdim
161224145Sdim/*
162224145Sdim * Transfer file
163224145Sdim */
164224145Sdimstatic FILE *
165224145Sdim_ftp_transfer(FILE *cf, char *oper, char *file, char *mode, int pasv)
166224145Sdim{
167245431Sdim    struct sockaddr_in sin;
168245431Sdim    int sd = -1, l;
169245431Sdim    char *s;
170224145Sdim    FILE *df;
171245431Sdim
172245431Sdim    /* change directory */
173245431Sdim    if (((s = strrchr(file, '/')) != NULL) && (s != file)) {
174224145Sdim	*s = 0;
175245431Sdim	if (_ftp_cmd(cf, "CWD %s" ENDL, file) != FTP_FILE_ACTION_OK) {
176245431Sdim	    *s = '/';
177224145Sdim	    return NULL;
178245431Sdim	}
179245431Sdim	*s++ = '/';
180224145Sdim    } else {
181245431Sdim	if (_ftp_cmd(cf, "CWD /" ENDL) != FTP_FILE_ACTION_OK)
182245431Sdim	    return NULL;
183245431Sdim    }
184224145Sdim
185245431Sdim    /* s now points to file name */
186245431Sdim
187245431Sdim    /* open data socket */
188245431Sdim    if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
189245431Sdim	_fetch_syserr();
190245431Sdim	return NULL;
191245431Sdim    }
192245431Sdim
193245431Sdim    if (pasv) {
194245431Sdim	u_char addr[6];
195245431Sdim	char *ln, *p;
196245431Sdim	int i;
197245431Sdim
198245431Sdim	/* send PASV command */
199245431Sdim	if (_ftp_cmd(cf, "PASV" ENDL) != FTP_PASSIVE_MODE)
200245431Sdim	    goto ouch;
201245431Sdim
202245431Sdim	/* find address and port number. The reply to the PASV command
203245431Sdim           is IMHO the one and only weak point in the FTP protocol. */
204245431Sdim	ln = _ftp_last_reply;
205245431Sdim	for (p = ln + 3; !isdigit(*p); p++)
206245431Sdim	    /* nothing */ ;
207245431Sdim	for (p--, i = 0; i < 6; i++) {
208210299Sed	    p++; /* skip the comma */
209245431Sdim	    addr[i] = strtol(p, &p, 10);
210245431Sdim	}
211208600Srdivacky
212245431Sdim	/* construct sockaddr for data socket */
213245431Sdim	l = sizeof(sin);
214208600Srdivacky	if (getpeername(fileno(cf), (struct sockaddr *)&sin, &l) == -1)
215208600Srdivacky	    goto sysouch;
216208600Srdivacky	bcopy(addr, (char *)&sin.sin_addr, 4);
217193326Sed	bcopy(addr + 4, (char *)&sin.sin_port, 2);
218224145Sdim
219193326Sed	/* connect to data port */
220198092Srdivacky	if (connect(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
221221345Sdim	    goto sysouch;
222221345Sdim
223224145Sdim	/* make the server initiate the transfer */
224221345Sdim	if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION)
225221345Sdim	    goto ouch;
226224145Sdim
227193326Sed    } else {
228193326Sed	u_int32_t a;
229210299Sed	u_short p;
230210299Sed	int d;
231210299Sed
232224145Sdim	/* find our own address, bind, and listen */
233210299Sed	l = sizeof(sin);
234210299Sed	if (getsockname(fileno(cf), (struct sockaddr *)&sin, &l) == -1)
235210299Sed	    goto sysouch;
236210299Sed	sin.sin_port = 0;
237210299Sed	if (bind(sd, (struct sockaddr *)&sin, l) == -1)
238210299Sed	    goto sysouch;
239210299Sed	if (listen(sd, 1) == -1)
240210299Sed	    goto sysouch;
241210299Sed
242210299Sed	/* find what port we're on and tell the server */
243210299Sed	if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1)
244210299Sed	    goto sysouch;
245193326Sed	a = ntohl(sin.sin_addr.s_addr);
246224145Sdim	p = ntohs(sin.sin_port);
247224145Sdim	if (_ftp_cmd(cf, "PORT %d,%d,%d,%d,%d,%d" ENDL,
248224145Sdim		     (a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff,
249224145Sdim		     (p >> 8) & 0xff, p & 0xff) != FTP_OK)
250224145Sdim	    goto ouch;
251224145Sdim
252224145Sdim	/* make the server initiate the transfer */
253224145Sdim	if (_ftp_cmd(cf, "%s %s" ENDL, oper, s) != FTP_OPEN_DATA_CONNECTION)
254224145Sdim	    goto ouch;
255224145Sdim
256224145Sdim	/* accept the incoming connection and go to town */
257224145Sdim	if ((d = accept(sd, NULL, NULL)) == -1)
258224145Sdim	    goto sysouch;
259224145Sdim	close(sd);
260224145Sdim	sd = d;
261224145Sdim    }
262224145Sdim
263224145Sdim    if ((df = fdopen(sd, mode)) == NULL)
264224145Sdim	goto sysouch;
265224145Sdim    return df;
266224145Sdim
267224145Sdimsysouch:
268224145Sdim    _fetch_syserr();
269224145Sdimouch:
270224145Sdim    close(sd);
271224145Sdim    return NULL;
272235633Sdim}
273224145Sdim
274224145Sdim/*
275224145Sdim * Log on to FTP server
276224145Sdim */
277224145Sdimstatic FILE *
278224145Sdim_ftp_connect(char *host, int port, char *user, char *pwd, int verbose)
279224145Sdim{
280224145Sdim    int sd, e, pp = FTP_DEFAULT_PORT;
281224145Sdim    char *p, *q;
282224145Sdim    FILE *f;
283193326Sed
284193326Sed    /* check for proxy */
285193326Sed    if ((p = getenv("FTP_PROXY")) != NULL) {
286193326Sed	if ((q = strchr(p, ':')) != NULL) {
287224145Sdim	    /* XXX check that it's a valid number */
288224145Sdim	    pp = atoi(q+1);
289224145Sdim	}
290224145Sdim	if (q)
291224145Sdim	    *q = 0;
292224145Sdim	sd = fetchConnect(p, pp, verbose);
293224145Sdim	if (q)
294224145Sdim	    *q = ':';
295210299Sed    } else {
296210299Sed	/* no proxy, go straight to target */
297193326Sed	sd = fetchConnect(host, port, verbose);
298208600Srdivacky    }
299208600Srdivacky
300208600Srdivacky    /* check connection */
301208600Srdivacky    if (sd == -1) {
302208600Srdivacky	_fetch_syserr();
303208600Srdivacky	return NULL;
304218893Sdim    }
305218893Sdim
306218893Sdim    /* streams make life easier */
307218893Sdim    if ((f = fdopen(sd, "r+")) == NULL) {
308218893Sdim	_fetch_syserr();
309218893Sdim	goto ouch;
310218893Sdim    }
311218893Sdim
312218893Sdim    /* expect welcome message */
313218893Sdim    if (_ftp_chkerr(f, NULL) == -1)
314218893Sdim	goto fouch;
315208600Srdivacky
316208600Srdivacky    /* send user name and password */
317208600Srdivacky    if (!user || !*user)
318208600Srdivacky	user = FTP_ANONYMOUS_USER;
319210299Sed    e = p ? _ftp_cmd(f, "USER %s@%s@%d" ENDL, user, host, port)
320210299Sed	  : _ftp_cmd(f, "USER %s" ENDL, user);
321210299Sed
322210299Sed    /* did the server request a password? */
323210299Sed    if (e == FTP_NEED_PASSWORD) {
324210299Sed	if (!pwd || !*pwd)
325210299Sed	    pwd = FTP_ANONYMOUS_PASSWORD;
326210299Sed	e = _ftp_cmd(f, "PASS %s" ENDL, pwd);
327210299Sed    }
328208600Srdivacky
329193326Sed    /* did the server request an account? */
330193326Sed    if (e == FTP_NEED_ACCOUNT)
331193326Sed	/* help! */ ;
332193326Sed
333208600Srdivacky    /* we should be done by now */
334208600Srdivacky    if (e != FTP_LOGGED_IN)
335245431Sdim	goto fouch;
336245431Sdim
337245431Sdim    /* might as well select mode and type at once */
338245431Sdim#ifdef FTP_FORCE_STREAM_MODE
339245431Sdim    if (_ftp_cmd(f, "MODE S" ENDL) != FTP_OK) /* default is S */
340245431Sdim	goto ouch;
341245431Sdim#endif
342245431Sdim    if (_ftp_cmd(f, "TYPE I" ENDL) != FTP_OK) /* default is A */
343245431Sdim	goto ouch;
344245431Sdim
345245431Sdim    /* done */
346245431Sdim    return f;
347245431Sdim
348245431Sdimouch:
349245431Sdim    close(sd);
350245431Sdim    return NULL;
351245431Sdimfouch:
352245431Sdim    fclose(f);
353245431Sdim    return NULL;
354245431Sdim}
355245431Sdim
356245431Sdim/*
357245431Sdim * Disconnect from server
358208600Srdivacky */
359208600Srdivackystatic void
360193326Sed_ftp_disconnect(FILE *f)
361198092Srdivacky{
362218893Sdim    _ftp_cmd(f, "QUIT" ENDL);
363210299Sed    fclose(f);
364210299Sed}
365210299Sed
366210299Sed/*
367210299Sed * Check if we're already connected
368193326Sed */
369193326Sedstatic int
370198092Srdivacky_ftp_isconnected(struct url *url)
371212904Sdim{
372212904Sdim    return (cached_socket
373212904Sdim	    && (strcmp(url->host, cached_host.host) == 0)
374212904Sdim	    && (strcmp(url->user, cached_host.user) == 0)
375212904Sdim	    && (strcmp(url->pwd, cached_host.pwd) == 0)
376218893Sdim	    && (url->port == cached_host.port));
377218893Sdim}
378218893Sdim
379218893Sdim/*
380218893Sdim * FTP session
381218893Sdim */
382212904Sdimstatic FILE *
383212904SdimfetchXxxFTP(struct url *url, char *oper, char *mode, char *flags)
384212904Sdim{
385212904Sdim    FILE *cf = NULL;
386212904Sdim    int e;
387212904Sdim
388212904Sdim    /* set default port */
389193326Sed    if (!url->port)
390193326Sed	url->port = FTP_DEFAULT_PORT;
391193326Sed
392204643Srdivacky    /* try to use previously cached connection; there should be a 226 waiting */
393204643Srdivacky    if (_ftp_isconnected(url)) {
394193326Sed	_ftp_chkerr(cached_socket, &e);
395210299Sed	if (e > 0)
396210299Sed	    cf = cached_socket;
397193326Sed    }
398193326Sed
399193326Sed    /* connect to server */
400193326Sed    if (!cf) {
401193326Sed	cf = _ftp_connect(url->host, url->port, url->user, url->pwd,
402224145Sdim			  (strchr(flags, 'v') != NULL));
403193326Sed	if (!cf)
404208600Srdivacky	    return NULL;
405208600Srdivacky	if (cached_socket)
406208600Srdivacky	    _ftp_disconnect(cached_socket);
407193326Sed	cached_socket = cf;
408193326Sed	memcpy(&cached_host, url, sizeof(struct url));
409224145Sdim    }
410193326Sed
411193326Sed    /* initiate the transfer */
412207619Srdivacky    return _ftp_transfer(cf, oper, url->doc, mode, (flags && strchr(flags, 'p')));
413207619Srdivacky}
414193326Sed
415193326Sed/*
416208600Srdivacky * Itsy bitsy teeny weenie
417193326Sed */
418198092SrdivackyFILE *
419224145SdimfetchGetFTP(struct url *url, char *flags)
420224145Sdim{
421193326Sed    return fetchXxxFTP(url, "RETR", "r", flags);
422198092Srdivacky}
423193326Sed
424193326SedFILE *
425193326SedfetchPutFTP(struct url *url, char *flags)
426193326Sed{
427208600Srdivacky    if (flags && strchr(flags, 'a'))
428193326Sed	return fetchXxxFTP(url, "APPE", "w", flags);
429224145Sdim    else return fetchXxxFTP(url, "STOR", "w", flags);
430193326Sed}
431198092Srdivacky
432193326Sedextern void warnx(char *fmt, ...);
433193326Sedint
434224145SdimfetchStatFTP(struct url *url, struct url_stat *us, char *flags)
435224145Sdim{
436193326Sed    warnx("fetchStatFTP(): not implemented");
437193326Sed    return -1;
438193326Sed}
439193326Sed
440193326Sed