diff options
Diffstat (limited to 'core/fs/pxe/ftp.c')
-rw-r--r-- | core/fs/pxe/ftp.c | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/core/fs/pxe/ftp.c b/core/fs/pxe/ftp.c new file mode 100644 index 00000000..5d5a6f03 --- /dev/null +++ b/core/fs/pxe/ftp.c @@ -0,0 +1,280 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2009-2011 Intel Corporation; author: H. Peter Anvin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston MA 02110-1301, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +/* + * ftp.c + */ +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <minmax.h> +#include <sys/cpu.h> +#include <netinet/in.h> +#include <lwip/api.h> +#include "core.h" +#include "fs.h" +#include "pxe.h" +#include "thread.h" +#include "url.h" + +static int ftp_cmd_response(struct inode *inode, const char *cmd, + const char *cmd_arg, + uint8_t *pasv_data, int *pn_ptr) +{ + struct pxe_pvt_inode *socket = PVT(inode); + int c; + int pos, code; + int pb, pn; + bool ps; + bool first_line, done; + err_t err; + char cmd_buf[4096]; + int cmd_len; + const char *p; + char *q; + + if (cmd) { + cmd_len = strlcpy(cmd_buf, cmd, sizeof cmd_buf); + if (cmd_len >= sizeof cmd_buf - 3) + return -1; + q = cmd_buf + cmd_len; + + if (cmd_arg) { + p = cmd_arg; + + *q++ = ' '; + cmd_len++; + while (*p) { + if (++cmd_len < sizeof cmd_buf) *q++ = *p; + if (*p == '\r') + if (++cmd_len < sizeof cmd_buf) *q++ = '\0'; + p++; + } + + if (cmd_len >= sizeof cmd_buf - 2) + return -1; + } + + *q++ = '\r'; + *q++ = '\n'; + cmd_len += 2; + + err = netconn_write(socket->private.conn, cmd_buf, cmd_len, NETCONN_COPY); + if (err) + return -1; + } + + pos = code = pn = pb = 0; + ps = false; + first_line = true; + done = false; + + while ((c = pxe_getc(inode)) >= 0) { + if (c == '\n') { + if (done) { + if (pn) { + pn += ps; + if (pn_ptr) + *pn_ptr = pn; + } + return code; + } + pos = code = 0; + first_line = false; + continue; + } + + switch (pos++) { + case 0: + case 1: + case 2: + if (c < '0' || c > '9') { + if (first_line) + return -1; + else + pos = 4; /* Skip this line */ + } else { + code = (code*10) + (c - '0'); + } + break; + + case 3: + pn = pb = 0; + ps = false; + if (c == ' ') + done = true; + else if (c == '-') + done = false; + else if (first_line) + return -1; + else + done = false; + break; + + default: + if (pasv_data) { + if (c >= '0' && c <= '9') { + pb = (pb*10) + (c-'0'); + if (pn < 6) + pasv_data[pn] = pb; + ps = true; + } else if (c == ',') { + pn++; + pb = 0; + ps = false; + } else if (pn) { + pn += ps; + if (pn_ptr) + *pn_ptr = pn; + pn = pb = 0; + ps = false; + } + } + break; + } + } + + return -1; +} + +static void ftp_free(struct inode *inode) +{ + struct pxe_pvt_inode *socket = PVT(inode); + + if (socket->ctl) { + tcp_close_file(socket->ctl); + free_socket(socket->ctl); + socket->ctl = NULL; + } + tcp_close_file(inode); +} + +static void ftp_close_file(struct inode *inode) +{ + struct pxe_pvt_inode *socket = PVT(inode); + struct pxe_pvt_inode *ctlsock; + int resp; + + ctlsock = socket->ctl ? PVT(socket->ctl) : NULL; + if (ctlsock->private.conn) { + resp = ftp_cmd_response(socket->ctl, "QUIT", NULL, NULL, NULL); + while (resp == 226) { + resp = ftp_cmd_response(socket->ctl, NULL, NULL, NULL, NULL); + } + } + ftp_free(inode); +} + +static const struct pxe_conn_ops ftp_conn_ops = { + .fill_buffer = tcp_fill_buffer, + .close = ftp_close_file, + .readdir = ftp_readdir, +}; + +void ftp_open(struct url_info *url, int flags, struct inode *inode, + const char **redir) +{ + struct pxe_pvt_inode *socket = PVT(inode); + struct pxe_pvt_inode *ctlsock; + struct ip_addr addr; + uint8_t pasv_data[6]; + int pasv_bytes; + int resp; + err_t err; + + (void)redir; /* FTP does not redirect */ + + inode->size = 0; + + if (!url->port) + url->port = 21; + + url_unescape(url->path, 0); + + socket->ops = &ftp_conn_ops; + + /* Set up the control connection */ + socket->ctl = alloc_inode(inode->fs, 0, sizeof(struct pxe_pvt_inode)); + if (!socket->ctl) + return; + ctlsock = PVT(socket->ctl); + ctlsock->ops = &tcp_conn_ops; /* The control connection is just TCP */ + ctlsock->private.conn = netconn_new(NETCONN_TCP); + if (!ctlsock->private.conn) + goto err_free; + addr.addr = url->ip; + err = netconn_connect(ctlsock->private.conn, &addr, url->port); + if (err) + goto err_delete; + + do { + resp = ftp_cmd_response(socket->ctl, NULL, NULL, NULL, NULL); + } while (resp == 120); + if (resp != 220) + goto err_disconnect; + + if (!url->user) + url->user = "anonymous"; + if (!url->passwd) + url->passwd = "syslinux@"; + + resp = ftp_cmd_response(socket->ctl, "USER", url->user, NULL, NULL); + if (resp != 202 && resp != 230) { + if (resp != 331) + goto err_disconnect; + + resp = ftp_cmd_response(socket->ctl, "PASS", url->passwd, NULL, NULL); + if (resp != 230) + goto err_disconnect; + } + + if (!(flags & O_DIRECTORY)) { + resp = ftp_cmd_response(socket->ctl, "TYPE", "I", NULL, NULL); + if (resp != 200) + goto err_disconnect; + } + + resp = ftp_cmd_response(socket->ctl, "PASV", NULL, pasv_data, &pasv_bytes); + if (resp != 227 || pasv_bytes != 6) + goto err_disconnect; + + socket->private.conn = netconn_new(NETCONN_TCP); + if (!socket->private.conn) + goto err_disconnect; + err = netconn_connect(socket->private.conn, (struct ip_addr *)&pasv_data[0], + ntohs(*(uint16_t *)&pasv_data[4])); + if (err) + goto err_disconnect; + + resp = ftp_cmd_response(socket->ctl, + (flags & O_DIRECTORY) ? "LIST" : "RETR", + url->path, NULL, NULL); + if (resp != 125 && resp != 150) + goto err_disconnect; + + inode->size = -1; + return; /* Sucess! */ + +err_disconnect: + if (ctlsock->private.conn) + netconn_write(ctlsock->private.conn, "QUIT\r\n", 6, NETCONN_NOCOPY); + if (socket->private.conn) + netconn_delete(socket->private.conn); + if (ctlsock->private.buf) + netbuf_delete(ctlsock->private.buf); +err_delete: + if (ctlsock->private.conn) + netconn_delete(ctlsock->private.conn); +err_free: + free_socket(socket->ctl); +} |