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