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