1   /********************************************************************\
2   * Axel -- A lighter download accelerator for Linux and other Unices. *
3   *                                                                    *
4   * Copyright 2001 Wilmer van der Gaast                                *
5   \********************************************************************/
6 
7 /* FTP control file							*/
8 
9 /*
10   This program is free software; you can redistribute it and/or modify
11   it under the terms of the GNU General Public License as published by
12   the Free Software Foundation; either version 2 of the License, or
13   (at your option) any later version.
14 
15   This program is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   GNU General Public License for more details.
19 
20   You should have received a copy of the GNU General Public License with
21   the Debian GNU/Linux distribution in file /usr/doc/copyright/GPL;
22   if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23   Suite 330, Boston, MA  02111-1307  USA
24 */
25 
26 #include "axel.h"
27 
ftp_connect(ftp_t * conn,char * host,int port,char * user,char * pass)28 int ftp_connect( ftp_t *conn, char *host, int port, char *user, char *pass )
29 {
30 	conn->data_fd = -1;
31 	conn->message = malloc( MAX_STRING );
32 
33 	if( ( conn->fd = tcp_connect( host, port, conn->local_if ) ) == -1 )
34 	{
35 		sprintf( conn->message, _("Unable to connect to server %s:%i\n"), host, port );
36 		return( 0 );
37 	}
38 
39 	if( ftp_wait( conn ) / 100 != 2 )
40 		return( 0 );
41 
42 	ftp_command( conn, "USER %s", user );
43 	if( ftp_wait( conn ) / 100 != 2 )
44 	{
45 		if( conn->status / 100 == 3 )
46 		{
47 			ftp_command( conn, "PASS %s", pass );
48 			if( ftp_wait( conn ) / 100 != 2 )
49 				return( 0 );
50 		}
51 		else
52 		{
53 			return( 0 );
54 		}
55 	}
56 
57 	/* ASCII mode sucks. Just use Binary.. */
58 	ftp_command( conn, "TYPE I" );
59 	if( ftp_wait( conn ) / 100 != 2 )
60 		return( 0 );
61 
62 	return( 1 );
63 }
64 
ftp_disconnect(ftp_t * conn)65 void ftp_disconnect( ftp_t *conn )
66 {
67 	if( conn->fd > 0 )
68 		close( conn->fd );
69 	if( conn->data_fd > 0 )
70 		close( conn->data_fd );
71 	if( conn->message )
72 	{
73 		free( conn->message );
74 		conn->message = NULL;
75 	}
76 
77 	*conn->cwd = 0;
78 	conn->fd = conn->data_fd = -1;
79 }
80 
81 /* Change current working directory					*/
ftp_cwd(ftp_t * conn,char * cwd)82 int ftp_cwd( ftp_t *conn, char *cwd )
83 {
84 	/* Necessary at all? */
85 	if( strncmp( conn->cwd, cwd, MAX_STRING ) == 0 )
86 		return( 1 );
87 
88 	ftp_command( conn, "CWD %s", cwd );
89 	if( ftp_wait( conn ) / 100 != 2 )
90 	{
91 		fprintf( stderr, _("Can't change directory to %s\n"), cwd );
92 		return( 0 );
93 	}
94 
95 	strncpy( conn->cwd, cwd, MAX_STRING );
96 
97 	return( 1 );
98 }
99 
100 /* Get file size. Should work with all reasonable servers now		*/
ftp_size(ftp_t * conn,char * file,int maxredir)101 long long int ftp_size( ftp_t *conn, char *file, int maxredir )
102 {
103 	long long int i, j, size = MAX_STRING;
104 	char *reply, *s, fn[MAX_STRING];
105 
106 	/* Try the SIZE command first, if possible			*/
107 	if( !strchr( file, '*' ) && !strchr( file, '?' ) )
108 	{
109 		ftp_command( conn, "SIZE %s", file );
110 		if( ftp_wait( conn ) / 100 == 2 )
111 		{
112 			sscanf( conn->message, "%*i %lld", &i );
113 			return( i );
114 		}
115 		else if( conn->status / 10 != 50 )
116 		{
117 			sprintf( conn->message, _("File not found.\n") );
118 			return( -1 );
119 		}
120 	}
121 
122 	if( maxredir == 0 )
123 	{
124 		sprintf( conn->message, _("Too many redirects.\n") );
125 		return( -1 );
126 	}
127 
128 	if( !ftp_data( conn ) )
129 		return( -1 );
130 
131 	ftp_command( conn, "LIST %s", file );
132 	if( ftp_wait( conn ) / 100 != 1 )
133 		return( -1 );
134 
135 	/* Read reply from the server.					*/
136 	reply = malloc( size );
137 	memset( reply, 0, size );
138 	*reply = '\n';
139 	i = 1;
140 	while( ( j = read( conn->data_fd, reply + i, size - i - 3 ) ) > 0 )
141 	{
142 		i += j;
143 		reply[i] = 0;
144 		if( size - i <= 10 )
145 		{
146 			size *= 2;
147 			reply = realloc( reply, size );
148 			memset( reply + size / 2, 0, size / 2 );
149 		}
150 	}
151 	close( conn->data_fd );
152 	conn->data_fd = -1;
153 
154 	if( ftp_wait( conn ) / 100 != 2 )
155 	{
156 		free( reply );
157 		return( -1 );
158 	}
159 
160 #ifdef DEBUG
161 	fprintf( stderr, reply );
162 #endif
163 
164 	/* Count the number of probably legal matches: Files&Links only	*/
165 	j = 0;
166 	for( i = 1; reply[i] && reply[i+1]; i ++ )
167 		if( reply[i] == '-' || reply[i] == 'l' )
168 			j ++;
169 		else
170 			while( reply[i] != '\n' && reply[i] )
171 				i ++;
172 
173 	/* No match or more than one match				*/
174 	if( j != 1 )
175 	{
176 		if( j == 0 )
177 			sprintf( conn->message, _("File not found.\n") );
178 		else
179 			sprintf( conn->message, _("Multiple matches for this URL.\n") );
180 		free( reply );
181 		return( -1 );
182 	}
183 
184 	/* Symlink handling						*/
185 	if( ( s = strstr( reply, "\nl" ) ) != NULL )
186 	{
187 		/* Get the real filename */
188 		sscanf( s, "%*s %*i %*s %*s %*i %*s %*i %*s %100s", fn );
189 		strcpy( file, fn );
190 
191 		/* Get size of the file linked to			*/
192 		strncpy( fn, strstr( s, "->" ) + 3, MAX_STRING );
193 		free( reply );
194 		if( ( reply = strchr( fn, '\r' ) ) != NULL )
195 			*reply = 0;
196 		if( ( reply = strchr( fn, '\n' ) ) != NULL )
197 			*reply = 0;
198 		return( ftp_size( conn, fn, maxredir - 1 ) );
199 	}
200 	/* Normal file, so read the size! And read filename because of
201 	   possible wildcards.						*/
202 	else
203 	{
204 		s = strstr( reply, "\n-" );
205 		i = sscanf( s, "%*s %*i %*s %*s %lld %*s %*i %*s %100s", &size, fn );
206 		if( i < 2 )
207 		{
208 			i = sscanf( s, "%*s %*i %lld %*i %*s %*i %*i %100s", &size, fn );
209 			if( i < 2 )
210 			{
211 				return( -2 );
212 			}
213 		}
214 		strcpy( file, fn );
215 
216 		free( reply );
217 		return( size );
218 	}
219 }
220 
221 /* Open a data connection. Only Passive mode supported yet, easier..	*/
ftp_data(ftp_t * conn)222 int ftp_data( ftp_t *conn )
223 {
224 	int i, info[6];
225 	char host[MAX_STRING];
226 
227 	/* Already done?						*/
228 	if( conn->data_fd > 0 )
229 		return( 0 );
230 
231 /*	if( conn->ftp_mode == FTP_PASSIVE )
232 	{
233 */		ftp_command( conn, "PASV" );
234 		if( ftp_wait( conn ) / 100 != 2 )
235 			return( 0 );
236 		*host = 0;
237 		for( i = 0; conn->message[i]; i ++ )
238 		{
239 			if( sscanf( &conn->message[i], "%i,%i,%i,%i,%i,%i",
240 			            &info[0], &info[1], &info[2], &info[3],
241 			            &info[4], &info[5] ) == 6 )
242 			{
243 				sprintf( host, "%i.%i.%i.%i",
244 				         info[0], info[1], info[2], info[3] );
245 				break;
246 			}
247 		}
248 		if( !*host )
249 		{
250 			sprintf( conn->message, _("Error opening passive data connection.\n") );
251 			return( 0 );
252 		}
253 		if( ( conn->data_fd = tcp_connect( host,
254 			info[4] * 256 + info[5], conn->local_if ) ) == -1 )
255 		{
256 			sprintf( conn->message, _("Error opening passive data connection.\n") );
257 			return( 0 );
258 		}
259 
260 		return( 1 );
261 /*	}
262 	else
263 	{
264 		sprintf( conn->message, _("Active FTP not implemented yet.\n" ) );
265 		return( 0 );
266 	} */
267 }
268 
269 /* Send a command to the server						*/
ftp_command(ftp_t * conn,char * format,...)270 int ftp_command( ftp_t *conn, char *format, ... )
271 {
272 	va_list params;
273 	char cmd[MAX_STRING];
274 
275 	va_start( params, format );
276 	vsnprintf( cmd, MAX_STRING - 3, format, params );
277 	strcat( cmd, "\r\n" );
278 	va_end( params );
279 
280 #ifdef DEBUG
281 	fprintf( stderr, "fd(%i)<--%s", conn->fd, cmd );
282 #endif
283 
284 	if( write( conn->fd, cmd, strlen( cmd ) ) != strlen( cmd ) )
285 	{
286 		sprintf( conn->message, _("Error writing command %s\n"), format );
287 		return( 0 );
288 	}
289 	else
290 	{
291 		return( 1 );
292 	}
293 }
294 
295 /* Read status from server. Should handle multi-line replies correctly.
296    Multi-line replies suck...						*/
ftp_wait(ftp_t * conn)297 int ftp_wait( ftp_t *conn )
298 {
299 	int size = MAX_STRING, r = 0, complete, i, j;
300 	char *s;
301 
302 	conn->message = realloc( conn->message, size );
303 
304 	do
305 	{
306 		do
307 		{
308 			r += i = read( conn->fd, conn->message + r, 1 );
309 			if( i <= 0 )
310 			{
311 				sprintf( conn->message, _("Connection gone.\n") );
312 				return( -1 );
313 			}
314 			if( ( r + 10 ) >= size )
315 			{
316 				size += MAX_STRING;
317 				conn->message = realloc( conn->message, size );
318 			}
319 		}
320 		while( conn->message[r-1] != '\n' );
321 		conn->message[r] = 0;
322 		sscanf( conn->message, "%i", &conn->status );
323 		if( conn->message[3] == ' ' )
324 			complete = 1;
325 		else
326 			complete = 0;
327 
328 		for( i = 0; conn->message[i]; i ++ ) if( conn->message[i] == '\n' )
329 		{
330 			if( complete == 1 )
331 			{
332 				complete = 2;
333 				break;
334 			}
335 			if( conn->message[i+4] == ' ' )
336 			{
337 				j = -1;
338 				sscanf( &conn->message[i+1], "%3i", &j );
339 				if( j == conn->status )
340 					complete = 1;
341 			}
342 		}
343 	}
344 	while( complete != 2 );
345 
346 #ifdef DEBUG
347 	fprintf( stderr, "fd(%i)-->%s", conn->fd, conn->message );
348 #endif
349 
350 	if( ( s = strchr( conn->message, '\n' ) ) != NULL )
351 		*s = 0;
352 	if( ( s = strchr( conn->message, '\r' ) ) != NULL )
353 		*s = 0;
354 	conn->message = realloc( conn->message, max( strlen( conn->message ) + 1, MAX_STRING ) );
355 
356 	return( conn->status );
357 }
358