Update TFTP and FTP to take the same temporary URI scheme as HTTP
[people/xl0/gpxe.git] / src / net / tcp / ftp.c
1 #include <stddef.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <vsprintf.h>
5 #include <assert.h>
6 #include <errno.h>
7 #include <gpxe/async.h>
8 #include <gpxe/buffer.h>
9 #include <gpxe/uri.h>
10 #include <gpxe/ftp.h>
11
12 /** @file
13  *
14  * File transfer protocol
15  *
16  */
17
18 /*****************************************************************************
19  *
20  * FTP control channel
21  *
22  */
23
24 /** FTP control channel strings
25  *
26  * These are used as printf() format strings.  Since only one of them
27  * (RETR) takes an argument, we always supply that argument to the
28  * snprintf() call.
29  */
30 static const char * ftp_strings[] = {
31         [FTP_CONNECT]   = "",
32         [FTP_USER]      = "USER anonymous\r\n",
33         [FTP_PASS]      = "PASS etherboot@etherboot.org\r\n",
34         [FTP_TYPE]      = "TYPE I\r\n",
35         [FTP_PASV]      = "PASV\r\n",
36         [FTP_RETR]      = "RETR %s\r\n", 
37         [FTP_QUIT]      = "QUIT\r\n",
38         [FTP_DONE]      = "",
39 };
40
41 /**
42  * Get FTP request from control TCP application
43  *
44  * @v app               TCP application
45  * @ret ftp             FTP request
46  */
47 static inline struct ftp_request * tcp_to_ftp ( struct tcp_application *app ) {
48         return container_of ( app, struct ftp_request, tcp );
49 }
50
51 /**
52  * Mark FTP operation as complete
53  *
54  * @v ftp               FTP request
55  * @v rc                Return status code
56  */
57 static void ftp_done ( struct ftp_request *ftp, int rc ) {
58
59         DBGC ( ftp, "FTP %p completed with status %d\n", ftp, rc );
60
61         /* Close both TCP connections */
62         tcp_close ( &ftp->tcp );
63         tcp_close ( &ftp->tcp_data );
64
65         /* Mark asynchronous operation as complete */
66         async_done ( &ftp->async, rc );
67 }
68
69 /**
70  * Parse FTP byte sequence value
71  *
72  * @v text      Text string
73  * @v value     Value buffer
74  * @v len       Length of value buffer
75  *
76  * This parses an FTP byte sequence value (e.g. the "aaa,bbb,ccc,ddd"
77  * form for IP addresses in PORT commands) into a byte sequence.  @c
78  * *text will be updated to point beyond the end of the parsed byte
79  * sequence.
80  *
81  * This function is safe in the presence of malformed data, though the
82  * output is undefined.
83  */
84 static void ftp_parse_value ( char **text, uint8_t *value, size_t len ) {
85         do {
86                 *(value++) = strtoul ( *text, text, 10 );
87                 if ( **text )
88                         (*text)++;
89         } while ( --len );
90 }
91
92 /**
93  * Handle an FTP control channel response
94  *
95  * @v ftp       FTP request
96  *
97  * This is called once we have received a complete response line.
98  */
99 static void ftp_reply ( struct ftp_request *ftp ) {
100         char status_major = ftp->status_text[0];
101
102         DBGC ( ftp, "FTP %p received status %s\n", ftp, ftp->status_text );
103
104         /* Ignore "intermediate" responses (1xx codes) */
105         if ( status_major == '1' )
106                 return;
107
108         /* Anything other than success (2xx) or, in the case of a
109          * repsonse to a "USER" command, a password prompt (3xx), is a
110          * fatal error.
111          */
112         if ( ! ( ( status_major == '2' ) ||
113                  ( ( status_major == '3' ) && ( ftp->state == FTP_USER ) ) ) ){
114                 /* Flag protocol error and close connections */
115                 ftp_done ( ftp, -EPROTO );
116         }
117
118         /* Open passive connection when we get "PASV" response */
119         if ( ftp->state == FTP_PASV ) {
120                 char *ptr = ftp->passive_text;
121                 union {
122                         struct sockaddr_in sin;
123                         struct sockaddr_tcpip st;
124                 } sa;
125                 int rc;
126
127                 sa.sin.sin_family = AF_INET;
128                 ftp_parse_value ( &ptr, ( uint8_t * ) &sa.sin.sin_addr,
129                                   sizeof ( sa.sin.sin_addr ) );
130                 ftp_parse_value ( &ptr, ( uint8_t * ) &sa.sin.sin_port,
131                                   sizeof ( sa.sin.sin_port ) );
132                 if ( ( rc = tcp_connect ( &ftp->tcp_data, &sa.st, 0 ) ) != 0 ){
133                         DBGC ( ftp, "FTP %p could not make data connection\n",
134                                ftp );
135                         ftp_done ( ftp, rc );
136                         return;
137                 }
138         }
139
140         /* Move to next state */
141         if ( ftp->state < FTP_DONE )
142                 ftp->state++;
143         ftp->already_sent = 0;
144
145         if ( ftp->state < FTP_DONE ) {
146                 DBGC ( ftp, "FTP %p sending ", ftp );
147                 DBGC ( ftp, ftp_strings[ftp->state], ftp->uri->path );
148         }
149
150         return;
151 }
152
153 /**
154  * Handle new data arriving on FTP control channel
155  *
156  * @v app       TCP application
157  * @v data      New data
158  * @v len       Length of new data
159  *
160  * Data is collected until a complete line is received, at which point
161  * its information is passed to ftp_reply().
162  */
163 static void ftp_newdata ( struct tcp_application *app,
164                           void *data, size_t len ) {
165         struct ftp_request *ftp = tcp_to_ftp ( app );
166         char *recvbuf = ftp->recvbuf;
167         size_t recvsize = ftp->recvsize;
168         char c;
169         
170         while ( len-- ) {
171                 c = * ( ( char * ) data++ );
172                 switch ( c ) {
173                 case '\r' :
174                 case '\n' :
175                         /* End of line: call ftp_reply() to handle
176                          * completed reply.  Avoid calling ftp_reply()
177                          * twice if we receive both \r and \n.
178                          */
179                         if ( recvsize == 0 )
180                                 ftp_reply ( ftp );
181                         /* Start filling up the status code buffer */
182                         recvbuf = ftp->status_text;
183                         recvsize = sizeof ( ftp->status_text ) - 1;
184                         break;
185                 case '(' :
186                         /* Start filling up the passive parameter buffer */
187                         recvbuf = ftp->passive_text;
188                         recvsize = sizeof ( ftp->passive_text ) - 1;
189                         break;
190                 case ')' :
191                         /* Stop filling the passive parameter buffer */
192                         recvsize = 0;
193                         break;
194                 default :
195                         /* Fill up buffer if applicable */
196                         if ( recvsize > 0 ) {
197                                 *(recvbuf++) = c;
198                                 recvsize--;
199                         }
200                         break;
201                 }
202         }
203
204         /* Store for next invocation */
205         ftp->recvbuf = recvbuf;
206         ftp->recvsize = recvsize;
207 }
208
209 /**
210  * Handle acknowledgement of data sent on FTP control channel
211  *
212  * @v app       TCP application
213  */
214 static void ftp_acked ( struct tcp_application *app, size_t len ) {
215         struct ftp_request *ftp = tcp_to_ftp ( app );
216         
217         /* Mark off ACKed portion of the currently-transmitted data */
218         ftp->already_sent += len;
219 }
220
221 /**
222  * Construct data to send on FTP control channel
223  *
224  * @v app       TCP application
225  * @v buf       Temporary data buffer
226  * @v len       Length of temporary data buffer
227  */
228 static void ftp_senddata ( struct tcp_application *app,
229                            void *buf, size_t len ) {
230         struct ftp_request *ftp = tcp_to_ftp ( app );
231
232         /* Send the as-yet-unACKed portion of the string for the
233          * current state.
234          */
235         len = snprintf ( buf, len, ftp_strings[ftp->state], ftp->uri->path );
236         tcp_send ( app, buf + ftp->already_sent, len - ftp->already_sent );
237 }
238
239 /**
240  * Handle control channel being closed
241  *
242  * @v app               TCP application
243  *
244  * When the control channel is closed, the data channel must also be
245  * closed, if it is currently open.
246  */
247 static void ftp_closed ( struct tcp_application *app, int rc ) {
248         struct ftp_request *ftp = tcp_to_ftp ( app );
249
250         DBGC ( ftp, "FTP %p control connection closed: %s\n",
251                ftp, strerror ( rc ) );
252
253         /* Complete FTP operation */
254         ftp_done ( ftp, rc );
255 }
256
257 /** FTP control channel operations */
258 static struct tcp_operations ftp_tcp_operations = {
259         .closed         = ftp_closed,
260         .acked          = ftp_acked,
261         .newdata        = ftp_newdata,
262         .senddata       = ftp_senddata,
263 };
264
265 /*****************************************************************************
266  *
267  * FTP data channel
268  *
269  */
270
271 /**
272  * Get FTP request from data TCP application
273  *
274  * @v app               TCP application
275  * @ret ftp             FTP request
276  */
277 static inline struct ftp_request *
278 tcp_to_ftp_data ( struct tcp_application *app ) {
279         return container_of ( app, struct ftp_request, tcp_data );
280 }
281
282 /**
283  * Handle data channel being closed
284  *
285  * @v app               TCP application
286  *
287  * When the data channel is closed, the control channel should be left
288  * alone; the server will send a completion message via the control
289  * channel which we'll pick up.
290  *
291  * If the data channel is closed due to an error, we abort the request.
292  */
293 static void ftp_data_closed ( struct tcp_application *app, int rc ) {
294         struct ftp_request *ftp = tcp_to_ftp_data ( app );
295
296         DBGC ( ftp, "FTP %p data connection closed: %s\n",
297                ftp, strerror ( rc ) );
298         
299         /* If there was an error, close control channel and record status */
300         if ( rc )
301                 ftp_done ( ftp, rc );
302 }
303
304 /**
305  * Handle new data arriving on the FTP data channel
306  *
307  * @v app       TCP application
308  * @v data      New data
309  * @v len       Length of new data
310  */
311 static void ftp_data_newdata ( struct tcp_application *app,
312                                void *data, size_t len ) {
313         struct ftp_request *ftp = tcp_to_ftp_data ( app );
314         int rc;
315
316         /* Fill data buffer */
317         if ( ( rc = fill_buffer ( ftp->buffer, data,
318                                   ftp->buffer->fill, len ) ) != 0 ){
319                 DBGC ( ftp, "FTP %p failed to fill data buffer: %s\n",
320                        ftp, strerror ( rc ) );
321                 ftp_done ( ftp, rc );
322                 return;
323         }
324 }
325
326 /** FTP data channel operations */
327 static struct tcp_operations ftp_data_tcp_operations = {
328         .closed         = ftp_data_closed,
329         .newdata        = ftp_data_newdata,
330 };
331
332 /*****************************************************************************
333  *
334  * API
335  *
336  */
337
338 /**
339  * Reap asynchronous operation
340  *
341  * @v async             Asynchronous operation
342  */
343 static void ftp_reap ( struct async *async ) {
344         struct ftp_request *ftp =
345                 container_of ( async, struct ftp_request, async );
346
347         free ( ftp );
348 }
349
350 /** FTP asynchronous operations */
351 static struct async_operations ftp_async_operations = {
352         .reap = ftp_reap,
353 };
354
355 #warning "Quick name resolution hack"
356 #include <byteswap.h>
357
358 /**
359  * Initiate an FTP connection
360  *
361  * @v uri               Uniform Resource Identifier
362  * @v buffer            Buffer into which to download file
363  * @v parent            Parent asynchronous operation
364  * @ret rc              Return status code
365  */
366 int ftp_get ( struct uri *uri, struct buffer *buffer, struct async *parent ) {
367         struct ftp_request *ftp = NULL;
368         int rc;
369
370         /* Sanity checks */
371         if ( ! uri->path ) {
372                 rc = -EINVAL;
373                 goto err;
374         }
375
376         /* Allocate and populate FTP structure */
377         ftp = malloc ( sizeof ( *ftp ) );
378         if ( ! ftp ) {
379                 rc = -ENOMEM;
380                 goto err;
381         }
382         memset ( ftp, 0, sizeof ( *ftp ) );
383         ftp->uri = uri;
384         ftp->buffer = buffer;
385         ftp->state = FTP_CONNECT;
386         ftp->already_sent = 0;
387         ftp->recvbuf = ftp->status_text;
388         ftp->recvsize = sizeof ( ftp->status_text ) - 1;
389         ftp->tcp.tcp_op = &ftp_tcp_operations;
390         ftp->tcp_data.tcp_op = &ftp_data_tcp_operations;
391
392 #warning "Quick name resolution hack"
393         union {
394                 struct sockaddr_tcpip st;
395                 struct sockaddr_in sin;
396         } server;
397         server.sin.sin_port = htons ( FTP_PORT );
398         server.sin.sin_family = AF_INET;
399         if ( inet_aton ( uri->host, &server.sin.sin_addr ) == 0 ) {
400                 rc = -EINVAL;
401                 goto err;
402         }
403
404         DBGC ( ftp, "FTP %p fetching %s\n", ftp, ftp->uri->path );
405
406         if ( ( rc = tcp_connect ( &ftp->tcp, &server.st, 0 ) ) != 0 )
407                 goto err;
408
409         async_init ( &ftp->async, &ftp_async_operations, parent );
410         return 0;
411
412  err:
413         DBGC ( ftp, "FTP %p could not create request: %s\n", 
414                ftp, strerror ( rc ) );
415         free ( ftp );
416         return rc;
417 }