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