1 | #include <stdint.h> |
---|
2 | #include <stdlib.h> |
---|
3 | #include <stdio.h> |
---|
4 | #include <string.h> |
---|
5 | #include <assert.h> |
---|
6 | #include <errno.h> |
---|
7 | #include <byteswap.h> |
---|
8 | #include <gpxe/socket.h> |
---|
9 | #include <gpxe/tcpip.h> |
---|
10 | #include <gpxe/in.h> |
---|
11 | #include <gpxe/xfer.h> |
---|
12 | #include <gpxe/open.h> |
---|
13 | #include <gpxe/uri.h> |
---|
14 | #include <gpxe/features.h> |
---|
15 | #include <gpxe/ftp.h> |
---|
16 | |
---|
17 | /** @file |
---|
18 | * |
---|
19 | * File transfer protocol |
---|
20 | * |
---|
21 | */ |
---|
22 | |
---|
23 | FEATURE ( FEATURE_PROTOCOL, "FTP", DHCP_EB_FEATURE_FTP, 1 ); |
---|
24 | |
---|
25 | /** |
---|
26 | * FTP states |
---|
27 | * |
---|
28 | * These @b must be sequential, i.e. a successful FTP session must |
---|
29 | * pass through each of these states in order. |
---|
30 | */ |
---|
31 | enum ftp_state { |
---|
32 | FTP_CONNECT = 0, |
---|
33 | FTP_USER, |
---|
34 | FTP_PASS, |
---|
35 | FTP_TYPE, |
---|
36 | FTP_PASV, |
---|
37 | FTP_RETR, |
---|
38 | FTP_WAIT, |
---|
39 | FTP_QUIT, |
---|
40 | FTP_DONE, |
---|
41 | }; |
---|
42 | |
---|
43 | /** |
---|
44 | * An FTP request |
---|
45 | * |
---|
46 | */ |
---|
47 | struct ftp_request { |
---|
48 | /** Reference counter */ |
---|
49 | struct refcnt refcnt; |
---|
50 | /** Data transfer interface */ |
---|
51 | struct xfer_interface xfer; |
---|
52 | |
---|
53 | /** URI being fetched */ |
---|
54 | struct uri *uri; |
---|
55 | /** FTP control channel interface */ |
---|
56 | struct xfer_interface control; |
---|
57 | /** FTP data channel interface */ |
---|
58 | struct xfer_interface data; |
---|
59 | |
---|
60 | /** Current state */ |
---|
61 | enum ftp_state state; |
---|
62 | /** Buffer to be filled with data received via the control channel */ |
---|
63 | char *recvbuf; |
---|
64 | /** Remaining size of recvbuf */ |
---|
65 | size_t recvsize; |
---|
66 | /** FTP status code, as text */ |
---|
67 | char status_text[5]; |
---|
68 | /** Passive-mode parameters, as text */ |
---|
69 | char passive_text[24]; /* "aaa,bbb,ccc,ddd,eee,fff" */ |
---|
70 | }; |
---|
71 | |
---|
72 | /** |
---|
73 | * Free FTP request |
---|
74 | * |
---|
75 | * @v refcnt Reference counter |
---|
76 | */ |
---|
77 | static void ftp_free ( struct refcnt *refcnt ) { |
---|
78 | struct ftp_request *ftp = |
---|
79 | container_of ( refcnt, struct ftp_request, refcnt ); |
---|
80 | |
---|
81 | DBGC ( ftp, "FTP %p freed\n", ftp ); |
---|
82 | |
---|
83 | uri_put ( ftp->uri ); |
---|
84 | free ( ftp ); |
---|
85 | } |
---|
86 | |
---|
87 | /** |
---|
88 | * Mark FTP operation as complete |
---|
89 | * |
---|
90 | * @v ftp FTP request |
---|
91 | * @v rc Return status code |
---|
92 | */ |
---|
93 | static void ftp_done ( struct ftp_request *ftp, int rc ) { |
---|
94 | |
---|
95 | DBGC ( ftp, "FTP %p completed (%s)\n", ftp, strerror ( rc ) ); |
---|
96 | |
---|
97 | /* Close all data transfer interfaces */ |
---|
98 | xfer_nullify ( &ftp->xfer ); |
---|
99 | xfer_close ( &ftp->xfer, rc ); |
---|
100 | xfer_nullify ( &ftp->control ); |
---|
101 | xfer_close ( &ftp->control, rc ); |
---|
102 | xfer_nullify ( &ftp->data ); |
---|
103 | xfer_close ( &ftp->data, rc ); |
---|
104 | } |
---|
105 | |
---|
106 | /***************************************************************************** |
---|
107 | * |
---|
108 | * FTP control channel |
---|
109 | * |
---|
110 | */ |
---|
111 | |
---|
112 | /** An FTP control channel string */ |
---|
113 | struct ftp_control_string { |
---|
114 | /** Literal portion */ |
---|
115 | const char *literal; |
---|
116 | /** Variable portion |
---|
117 | * |
---|
118 | * @v ftp FTP request |
---|
119 | * @ret string Variable portion of string |
---|
120 | */ |
---|
121 | const char * ( *variable ) ( struct ftp_request *ftp ); |
---|
122 | }; |
---|
123 | |
---|
124 | /** |
---|
125 | * Retrieve FTP pathname |
---|
126 | * |
---|
127 | * @v ftp FTP request |
---|
128 | * @ret path FTP pathname |
---|
129 | */ |
---|
130 | static const char * ftp_uri_path ( struct ftp_request *ftp ) { |
---|
131 | return ftp->uri->path; |
---|
132 | } |
---|
133 | |
---|
134 | /** |
---|
135 | * Retrieve FTP user |
---|
136 | * |
---|
137 | * @v ftp FTP request |
---|
138 | * @ret user FTP user |
---|
139 | */ |
---|
140 | static const char * ftp_user ( struct ftp_request *ftp ) { |
---|
141 | static char *ftp_default_user = "anonymous"; |
---|
142 | return ftp->uri->user ? ftp->uri->user : ftp_default_user; |
---|
143 | } |
---|
144 | |
---|
145 | /** |
---|
146 | * Retrieve FTP password |
---|
147 | * |
---|
148 | * @v ftp FTP request |
---|
149 | * @ret password FTP password |
---|
150 | */ |
---|
151 | static const char * ftp_password ( struct ftp_request *ftp ) { |
---|
152 | static char *ftp_default_password = "etherboot@etherboot.org"; |
---|
153 | return ftp->uri->password ? ftp->uri->password : ftp_default_password; |
---|
154 | } |
---|
155 | |
---|
156 | /** FTP control channel strings */ |
---|
157 | static struct ftp_control_string ftp_strings[] = { |
---|
158 | [FTP_CONNECT] = { NULL, NULL }, |
---|
159 | [FTP_USER] = { "USER ", ftp_user }, |
---|
160 | [FTP_PASS] = { "PASS ", ftp_password }, |
---|
161 | [FTP_TYPE] = { "TYPE I", NULL }, |
---|
162 | [FTP_PASV] = { "PASV", NULL }, |
---|
163 | [FTP_RETR] = { "RETR ", ftp_uri_path }, |
---|
164 | [FTP_WAIT] = { NULL, NULL }, |
---|
165 | [FTP_QUIT] = { "QUIT", NULL }, |
---|
166 | [FTP_DONE] = { NULL, NULL }, |
---|
167 | }; |
---|
168 | |
---|
169 | /** |
---|
170 | * Handle control channel being closed |
---|
171 | * |
---|
172 | * @v control FTP control channel interface |
---|
173 | * @v rc Reason for close |
---|
174 | * |
---|
175 | * When the control channel is closed, the data channel must also be |
---|
176 | * closed, if it is currently open. |
---|
177 | */ |
---|
178 | static void ftp_control_close ( struct xfer_interface *control, int rc ) { |
---|
179 | struct ftp_request *ftp = |
---|
180 | container_of ( control, struct ftp_request, control ); |
---|
181 | |
---|
182 | DBGC ( ftp, "FTP %p control connection closed: %s\n", |
---|
183 | ftp, strerror ( rc ) ); |
---|
184 | |
---|
185 | /* Complete FTP operation */ |
---|
186 | ftp_done ( ftp, rc ); |
---|
187 | } |
---|
188 | |
---|
189 | /** |
---|
190 | * Parse FTP byte sequence value |
---|
191 | * |
---|
192 | * @v text Text string |
---|
193 | * @v value Value buffer |
---|
194 | * @v len Length of value buffer |
---|
195 | * |
---|
196 | * This parses an FTP byte sequence value (e.g. the "aaa,bbb,ccc,ddd" |
---|
197 | * form for IP addresses in PORT commands) into a byte sequence. @c |
---|
198 | * *text will be updated to point beyond the end of the parsed byte |
---|
199 | * sequence. |
---|
200 | * |
---|
201 | * This function is safe in the presence of malformed data, though the |
---|
202 | * output is undefined. |
---|
203 | */ |
---|
204 | static void ftp_parse_value ( char **text, uint8_t *value, size_t len ) { |
---|
205 | do { |
---|
206 | *(value++) = strtoul ( *text, text, 10 ); |
---|
207 | if ( **text ) |
---|
208 | (*text)++; |
---|
209 | } while ( --len ); |
---|
210 | } |
---|
211 | |
---|
212 | /** |
---|
213 | * Move to next state and send the appropriate FTP control string |
---|
214 | * |
---|
215 | * @v ftp FTP request |
---|
216 | * |
---|
217 | */ |
---|
218 | static void ftp_next_state ( struct ftp_request *ftp ) { |
---|
219 | struct ftp_control_string *ftp_string; |
---|
220 | const char *literal; |
---|
221 | const char *variable; |
---|
222 | |
---|
223 | /* Move to next state */ |
---|
224 | if ( ftp->state < FTP_DONE ) |
---|
225 | ftp->state++; |
---|
226 | |
---|
227 | /* Send control string if needed */ |
---|
228 | ftp_string = &ftp_strings[ftp->state]; |
---|
229 | literal = ftp_string->literal; |
---|
230 | variable = ( ftp_string->variable ? |
---|
231 | ftp_string->variable ( ftp ) : "" ); |
---|
232 | if ( literal ) { |
---|
233 | DBGC ( ftp, "FTP %p sending %s%s\n", ftp, literal, variable ); |
---|
234 | xfer_printf ( &ftp->control, "%s%s\r\n", literal, variable ); |
---|
235 | } |
---|
236 | } |
---|
237 | |
---|
238 | /** |
---|
239 | * Handle an FTP control channel response |
---|
240 | * |
---|
241 | * @v ftp FTP request |
---|
242 | * |
---|
243 | * This is called once we have received a complete response line. |
---|
244 | */ |
---|
245 | static void ftp_reply ( struct ftp_request *ftp ) { |
---|
246 | char status_major = ftp->status_text[0]; |
---|
247 | char separator = ftp->status_text[3]; |
---|
248 | |
---|
249 | DBGC ( ftp, "FTP %p received status %s\n", ftp, ftp->status_text ); |
---|
250 | |
---|
251 | /* Ignore malformed lines */ |
---|
252 | if ( separator != ' ' ) |
---|
253 | return; |
---|
254 | |
---|
255 | /* Ignore "intermediate" responses (1xx codes) */ |
---|
256 | if ( status_major == '1' ) |
---|
257 | return; |
---|
258 | |
---|
259 | /* Anything other than success (2xx) or, in the case of a |
---|
260 | * repsonse to a "USER" command, a password prompt (3xx), is a |
---|
261 | * fatal error. |
---|
262 | */ |
---|
263 | if ( ! ( ( status_major == '2' ) || |
---|
264 | ( ( status_major == '3' ) && ( ftp->state == FTP_USER ) ) ) ){ |
---|
265 | /* Flag protocol error and close connections */ |
---|
266 | ftp_done ( ftp, -EPROTO ); |
---|
267 | return; |
---|
268 | } |
---|
269 | |
---|
270 | /* Open passive connection when we get "PASV" response */ |
---|
271 | if ( ftp->state == FTP_PASV ) { |
---|
272 | char *ptr = ftp->passive_text; |
---|
273 | union { |
---|
274 | struct sockaddr_in sin; |
---|
275 | struct sockaddr sa; |
---|
276 | } sa; |
---|
277 | int rc; |
---|
278 | |
---|
279 | sa.sin.sin_family = AF_INET; |
---|
280 | ftp_parse_value ( &ptr, ( uint8_t * ) &sa.sin.sin_addr, |
---|
281 | sizeof ( sa.sin.sin_addr ) ); |
---|
282 | ftp_parse_value ( &ptr, ( uint8_t * ) &sa.sin.sin_port, |
---|
283 | sizeof ( sa.sin.sin_port ) ); |
---|
284 | if ( ( rc = xfer_open_socket ( &ftp->data, SOCK_STREAM, |
---|
285 | &sa.sa, NULL ) ) != 0 ) { |
---|
286 | DBGC ( ftp, "FTP %p could not open data connection\n", |
---|
287 | ftp ); |
---|
288 | ftp_done ( ftp, rc ); |
---|
289 | return; |
---|
290 | } |
---|
291 | } |
---|
292 | |
---|
293 | /* Move to next state and send control string */ |
---|
294 | ftp_next_state ( ftp ); |
---|
295 | |
---|
296 | } |
---|
297 | |
---|
298 | /** |
---|
299 | * Handle new data arriving on FTP control channel |
---|
300 | * |
---|
301 | * @v control FTP control channel interface |
---|
302 | * @v data New data |
---|
303 | * @v len Length of new data |
---|
304 | * |
---|
305 | * Data is collected until a complete line is received, at which point |
---|
306 | * its information is passed to ftp_reply(). |
---|
307 | */ |
---|
308 | static int ftp_control_deliver_raw ( struct xfer_interface *control, |
---|
309 | const void *data, size_t len ) { |
---|
310 | struct ftp_request *ftp = |
---|
311 | container_of ( control, struct ftp_request, control ); |
---|
312 | char *recvbuf = ftp->recvbuf; |
---|
313 | size_t recvsize = ftp->recvsize; |
---|
314 | char c; |
---|
315 | |
---|
316 | while ( len-- ) { |
---|
317 | c = * ( ( char * ) data++ ); |
---|
318 | switch ( c ) { |
---|
319 | case '\r' : |
---|
320 | case '\n' : |
---|
321 | /* End of line: call ftp_reply() to handle |
---|
322 | * completed reply. Avoid calling ftp_reply() |
---|
323 | * twice if we receive both \r and \n. |
---|
324 | */ |
---|
325 | if ( recvsize == 0 ) |
---|
326 | ftp_reply ( ftp ); |
---|
327 | /* Start filling up the status code buffer */ |
---|
328 | recvbuf = ftp->status_text; |
---|
329 | recvsize = sizeof ( ftp->status_text ) - 1; |
---|
330 | break; |
---|
331 | case '(' : |
---|
332 | /* Start filling up the passive parameter buffer */ |
---|
333 | recvbuf = ftp->passive_text; |
---|
334 | recvsize = sizeof ( ftp->passive_text ) - 1; |
---|
335 | break; |
---|
336 | case ')' : |
---|
337 | /* Stop filling the passive parameter buffer */ |
---|
338 | recvsize = 0; |
---|
339 | break; |
---|
340 | default : |
---|
341 | /* Fill up buffer if applicable */ |
---|
342 | if ( recvsize > 0 ) { |
---|
343 | *(recvbuf++) = c; |
---|
344 | recvsize--; |
---|
345 | } |
---|
346 | break; |
---|
347 | } |
---|
348 | } |
---|
349 | |
---|
350 | /* Store for next invocation */ |
---|
351 | ftp->recvbuf = recvbuf; |
---|
352 | ftp->recvsize = recvsize; |
---|
353 | |
---|
354 | return 0; |
---|
355 | } |
---|
356 | |
---|
357 | /** FTP control channel operations */ |
---|
358 | static struct xfer_interface_operations ftp_control_operations = { |
---|
359 | .close = ftp_control_close, |
---|
360 | .vredirect = xfer_vreopen, |
---|
361 | .window = unlimited_xfer_window, |
---|
362 | .alloc_iob = default_xfer_alloc_iob, |
---|
363 | .deliver_iob = xfer_deliver_as_raw, |
---|
364 | .deliver_raw = ftp_control_deliver_raw, |
---|
365 | }; |
---|
366 | |
---|
367 | /***************************************************************************** |
---|
368 | * |
---|
369 | * FTP data channel |
---|
370 | * |
---|
371 | */ |
---|
372 | |
---|
373 | /** |
---|
374 | * Handle FTP data channel being closed |
---|
375 | * |
---|
376 | * @v data FTP data channel interface |
---|
377 | * @v rc Reason for closure |
---|
378 | * |
---|
379 | * When the data channel is closed, the control channel should be left |
---|
380 | * alone; the server will send a completion message via the control |
---|
381 | * channel which we'll pick up. |
---|
382 | * |
---|
383 | * If the data channel is closed due to an error, we abort the request. |
---|
384 | */ |
---|
385 | static void ftp_data_closed ( struct xfer_interface *data, int rc ) { |
---|
386 | struct ftp_request *ftp = |
---|
387 | container_of ( data, struct ftp_request, data ); |
---|
388 | |
---|
389 | DBGC ( ftp, "FTP %p data connection closed: %s\n", |
---|
390 | ftp, strerror ( rc ) ); |
---|
391 | |
---|
392 | /* If there was an error, close control channel and record status */ |
---|
393 | if ( rc ) { |
---|
394 | ftp_done ( ftp, rc ); |
---|
395 | } else { |
---|
396 | ftp_next_state ( ftp ); |
---|
397 | } |
---|
398 | } |
---|
399 | |
---|
400 | /** |
---|
401 | * Handle data delivery via FTP data channel |
---|
402 | * |
---|
403 | * @v xfer FTP data channel interface |
---|
404 | * @v iobuf I/O buffer |
---|
405 | * @v meta Data transfer metadata |
---|
406 | * @ret rc Return status code |
---|
407 | */ |
---|
408 | static int ftp_data_deliver_iob ( struct xfer_interface *data, |
---|
409 | struct io_buffer *iobuf, |
---|
410 | struct xfer_metadata *meta __unused ) { |
---|
411 | struct ftp_request *ftp = |
---|
412 | container_of ( data, struct ftp_request, data ); |
---|
413 | int rc; |
---|
414 | |
---|
415 | if ( ( rc = xfer_deliver_iob ( &ftp->xfer, iobuf ) ) != 0 ) { |
---|
416 | DBGC ( ftp, "FTP %p failed to deliver data: %s\n", |
---|
417 | ftp, strerror ( rc ) ); |
---|
418 | return rc; |
---|
419 | } |
---|
420 | |
---|
421 | return 0; |
---|
422 | } |
---|
423 | |
---|
424 | /** FTP data channel operations */ |
---|
425 | static struct xfer_interface_operations ftp_data_operations = { |
---|
426 | .close = ftp_data_closed, |
---|
427 | .vredirect = xfer_vreopen, |
---|
428 | .window = unlimited_xfer_window, |
---|
429 | .alloc_iob = default_xfer_alloc_iob, |
---|
430 | .deliver_iob = ftp_data_deliver_iob, |
---|
431 | .deliver_raw = xfer_deliver_as_iob, |
---|
432 | }; |
---|
433 | |
---|
434 | /***************************************************************************** |
---|
435 | * |
---|
436 | * Data transfer interface |
---|
437 | * |
---|
438 | */ |
---|
439 | |
---|
440 | /** |
---|
441 | * Close FTP data transfer interface |
---|
442 | * |
---|
443 | * @v xfer FTP data transfer interface |
---|
444 | * @v rc Reason for close |
---|
445 | */ |
---|
446 | static void ftp_xfer_closed ( struct xfer_interface *xfer, int rc ) { |
---|
447 | struct ftp_request *ftp = |
---|
448 | container_of ( xfer, struct ftp_request, xfer ); |
---|
449 | |
---|
450 | DBGC ( ftp, "FTP %p data transfer interface closed: %s\n", |
---|
451 | ftp, strerror ( rc ) ); |
---|
452 | |
---|
453 | ftp_done ( ftp, rc ); |
---|
454 | } |
---|
455 | |
---|
456 | /** FTP data transfer interface operations */ |
---|
457 | static struct xfer_interface_operations ftp_xfer_operations = { |
---|
458 | .close = ftp_xfer_closed, |
---|
459 | .vredirect = ignore_xfer_vredirect, |
---|
460 | .window = unlimited_xfer_window, |
---|
461 | .alloc_iob = default_xfer_alloc_iob, |
---|
462 | .deliver_iob = xfer_deliver_as_raw, |
---|
463 | .deliver_raw = ignore_xfer_deliver_raw, |
---|
464 | }; |
---|
465 | |
---|
466 | /***************************************************************************** |
---|
467 | * |
---|
468 | * URI opener |
---|
469 | * |
---|
470 | */ |
---|
471 | |
---|
472 | /** |
---|
473 | * Initiate an FTP connection |
---|
474 | * |
---|
475 | * @v xfer Data transfer interface |
---|
476 | * @v uri Uniform Resource Identifier |
---|
477 | * @ret rc Return status code |
---|
478 | */ |
---|
479 | static int ftp_open ( struct xfer_interface *xfer, struct uri *uri ) { |
---|
480 | struct ftp_request *ftp; |
---|
481 | struct sockaddr_tcpip server; |
---|
482 | int rc; |
---|
483 | |
---|
484 | /* Sanity checks */ |
---|
485 | if ( ! uri->path ) |
---|
486 | return -EINVAL; |
---|
487 | if ( ! uri->host ) |
---|
488 | return -EINVAL; |
---|
489 | |
---|
490 | /* Allocate and populate structure */ |
---|
491 | ftp = zalloc ( sizeof ( *ftp ) ); |
---|
492 | if ( ! ftp ) |
---|
493 | return -ENOMEM; |
---|
494 | ftp->refcnt.free = ftp_free; |
---|
495 | xfer_init ( &ftp->xfer, &ftp_xfer_operations, &ftp->refcnt ); |
---|
496 | ftp->uri = uri_get ( uri ); |
---|
497 | xfer_init ( &ftp->control, &ftp_control_operations, &ftp->refcnt ); |
---|
498 | xfer_init ( &ftp->data, &ftp_data_operations, &ftp->refcnt ); |
---|
499 | ftp->recvbuf = ftp->status_text; |
---|
500 | ftp->recvsize = sizeof ( ftp->status_text ) - 1; |
---|
501 | |
---|
502 | DBGC ( ftp, "FTP %p fetching %s\n", ftp, ftp->uri->path ); |
---|
503 | |
---|
504 | /* Open control connection */ |
---|
505 | memset ( &server, 0, sizeof ( server ) ); |
---|
506 | server.st_port = htons ( uri_port ( uri, FTP_PORT ) ); |
---|
507 | if ( ( rc = xfer_open_named_socket ( &ftp->control, SOCK_STREAM, |
---|
508 | ( struct sockaddr * ) &server, |
---|
509 | uri->host, NULL ) ) != 0 ) |
---|
510 | goto err; |
---|
511 | |
---|
512 | /* Attach to parent interface, mortalise self, and return */ |
---|
513 | xfer_plug_plug ( &ftp->xfer, xfer ); |
---|
514 | ref_put ( &ftp->refcnt ); |
---|
515 | return 0; |
---|
516 | |
---|
517 | err: |
---|
518 | DBGC ( ftp, "FTP %p could not create request: %s\n", |
---|
519 | ftp, strerror ( rc ) ); |
---|
520 | ftp_done ( ftp, rc ); |
---|
521 | ref_put ( &ftp->refcnt ); |
---|
522 | return rc; |
---|
523 | } |
---|
524 | |
---|
525 | /** FTP URI opener */ |
---|
526 | struct uri_opener ftp_uri_opener __uri_opener = { |
---|
527 | .scheme = "ftp", |
---|
528 | .open = ftp_open, |
---|
529 | }; |
---|