| /* ----------------------------------------------------------------------- * |
| * |
| * 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" |
| #include "net.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; |
| char cmd_buf[4096]; |
| int cmd_len; |
| const char *p; |
| char *q; |
| int err; |
| |
| 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 = core_tcp_write(socket, cmd_buf, cmd_len, true); |
| 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) { |
| core_tcp_close_file(socket->ctl); |
| free_socket(socket->ctl); |
| socket->ctl = NULL; |
| } |
| core_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 (core_tcp_is_connected(ctlsock)) { |
| 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 = core_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; |
| 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 */ |
| if (core_tcp_open(ctlsock)) |
| goto err_free; |
| err = core_tcp_connect(ctlsock, url->ip, 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; |
| |
| err = core_tcp_open(socket); |
| if (err) |
| goto err_disconnect; |
| err = core_tcp_connect(socket, *(uint32_t*)&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: |
| core_tcp_write(ctlsock, "QUIT\r\n", 6, false); |
| core_tcp_close_file(inode); |
| err_delete: |
| core_tcp_close_file(socket->ctl); |
| err_free: |
| free_socket(socket->ctl); |
| } |