ee6c7eb3209beda8e3491ea1dda5a7e7bf8107e3
[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  */
224 static void ftp_senddata ( struct tcp_connection *conn ) {
225         struct ftp_request *ftp = tcp_to_ftp ( conn );
226         const struct ftp_string *string;
227         size_t len;
228
229         /* Send the as-yet-unACKed portion of the string for the
230          * current state.
231          */
232         string = &ftp_strings[ftp->state];
233         len = snprintf ( tcp_buffer, tcp_buflen, string->format,
234                          ftp_string_data ( ftp, string->data_offset ) );
235         tcp_send ( conn, tcp_buffer + ftp->already_sent,
236                    len - ftp->already_sent );
237 }
238
239 /**
240  * Handle control channel being closed
241  *
242  * @v conn              TCP connection
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_connection *conn, int status ) {
248         struct ftp_request *ftp = tcp_to_ftp ( conn );
249
250         ftp_complete ( ftp, status ? status : 1 );
251 }
252
253 /** FTP control channel operations */
254 static struct tcp_operations ftp_tcp_operations = {
255         .closed         = ftp_closed,
256         .acked          = ftp_acked,
257         .newdata        = ftp_newdata,
258         .senddata       = ftp_senddata,
259 };
260
261 /*****************************************************************************
262  *
263  * FTP data channel
264  *
265  */
266
267 /**
268  * Get FTP request from data TCP connection
269  *
270  * @v conn              TCP connection
271  * @ret ftp             FTP request
272  */
273 static inline struct ftp_request *
274 tcp_to_ftp_data ( struct tcp_connection *conn ) {
275         return container_of ( conn, struct ftp_request, tcp_data );
276 }
277
278 /**
279  * Handle data channel being closed
280  *
281  * @v conn              TCP connection
282  *
283  * When the data channel is closed, the control channel should be left
284  * alone; the server will send a completion message via the control
285  * channel which we'll pick up.
286  *
287  * If the data channel is closed due to an error, we abort the request.
288  */
289 static void ftp_data_closed ( struct tcp_connection *conn, int status ) {
290         struct ftp_request *ftp = tcp_to_ftp_data ( conn );
291
292         if ( status )
293                 ftp_complete ( ftp, status );
294 }
295
296 /**
297  * Handle new data arriving on the FTP data channel
298  *
299  * @v conn      TCP connection
300  * @v data      New data
301  * @v len       Length of new data
302  *
303  * Data is handed off to the callback registered in the FTP request.
304  */
305 static void ftp_data_newdata ( struct tcp_connection *conn,
306                                void *data, size_t len ) {
307         struct ftp_request *ftp = tcp_to_ftp_data ( conn );
308
309         ftp->callback ( data, len );
310 }
311
312 /** FTP data channel operations */
313 static struct tcp_operations ftp_data_tcp_operations = {
314         .closed         = ftp_data_closed,
315         .newdata        = ftp_data_newdata,
316 };
317
318 /*****************************************************************************
319  *
320  * API
321  *
322  */
323
324 /**
325  * Initiate an FTP connection
326  *
327  * @v ftp       FTP request
328  */
329 void ftp_connect ( struct ftp_request *ftp ) {
330         ftp->tcp.tcp_op = &ftp_tcp_operations;
331         ftp->tcp_data.tcp_op = &ftp_data_tcp_operations;
332         ftp->recvbuf = ftp->status_text;
333         ftp->recvsize = sizeof ( ftp->status_text ) - 1;
334         tcp_connect ( &ftp->tcp );
335 }