summaryrefslogtreecommitdiff
path: root/core/fs/pxe/ftp.c
diff options
context:
space:
mode:
Diffstat (limited to 'core/fs/pxe/ftp.c')
-rw-r--r--core/fs/pxe/ftp.c280
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);
+}