1/* ----------------------------------------------------------------------- * 2 * 3 * Copyright 2009-2011 Intel Corporation; author: H. Peter Anvin 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, 8 * Boston MA 02110-1301, USA; either version 2 of the License, or 9 * (at your option) any later version; incorporated herein by reference. 10 * 11 * ----------------------------------------------------------------------- */ 12 13/* 14 * ftp.c 15 */ 16#include <ctype.h> 17#include <stdio.h> 18#include <string.h> 19#include <fcntl.h> 20#include <minmax.h> 21#include <sys/cpu.h> 22#include <netinet/in.h> 23#include <lwip/api.h> 24#include "core.h" 25#include "fs.h" 26#include "pxe.h" 27#include "thread.h" 28#include "url.h" 29#include "net.h" 30 31static int ftp_cmd_response(struct inode *inode, const char *cmd, 32 const char *cmd_arg, 33 uint8_t *pasv_data, int *pn_ptr) 34{ 35 struct pxe_pvt_inode *socket = PVT(inode); 36 int c; 37 int pos, code; 38 int pb, pn; 39 bool ps; 40 bool first_line, done; 41 char cmd_buf[4096]; 42 int cmd_len; 43 const char *p; 44 char *q; 45 int err; 46 47 if (cmd) { 48 cmd_len = strlcpy(cmd_buf, cmd, sizeof cmd_buf); 49 if (cmd_len >= sizeof cmd_buf - 3) 50 return -1; 51 q = cmd_buf + cmd_len; 52 53 if (cmd_arg) { 54 p = cmd_arg; 55 56 *q++ = ' '; 57 cmd_len++; 58 while (*p) { 59 if (++cmd_len < sizeof cmd_buf) *q++ = *p; 60 if (*p == '\r') 61 if (++cmd_len < sizeof cmd_buf) *q++ = '\0'; 62 p++; 63 } 64 65 if (cmd_len >= sizeof cmd_buf - 2) 66 return -1; 67 } 68 69 *q++ = '\r'; 70 *q++ = '\n'; 71 cmd_len += 2; 72 73 err = core_tcp_write(socket, cmd_buf, cmd_len, true); 74 if (err) 75 return -1; 76 } 77 78 pos = code = pn = pb = 0; 79 ps = false; 80 first_line = true; 81 done = false; 82 83 while ((c = pxe_getc(inode)) >= 0) { 84 if (c == '\n') { 85 if (done) { 86 if (pn) { 87 pn += ps; 88 if (pn_ptr) 89 *pn_ptr = pn; 90 } 91 return code; 92 } 93 pos = code = 0; 94 first_line = false; 95 continue; 96 } 97 98 switch (pos++) { 99 case 0: 100 case 1: 101 case 2: 102 if (c < '0' || c > '9') { 103 if (first_line) 104 return -1; 105 else 106 pos = 4; /* Skip this line */ 107 } else { 108 code = (code*10) + (c - '0'); 109 } 110 break; 111 112 case 3: 113 pn = pb = 0; 114 ps = false; 115 if (c == ' ') 116 done = true; 117 else if (c == '-') 118 done = false; 119 else if (first_line) 120 return -1; 121 else 122 done = false; 123 break; 124 125 default: 126 if (pasv_data) { 127 if (c >= '0' && c <= '9') { 128 pb = (pb*10) + (c-'0'); 129 if (pn < 6) 130 pasv_data[pn] = pb; 131 ps = true; 132 } else if (c == ',') { 133 pn++; 134 pb = 0; 135 ps = false; 136 } else if (pn) { 137 pn += ps; 138 if (pn_ptr) 139 *pn_ptr = pn; 140 pn = pb = 0; 141 ps = false; 142 } 143 } 144 break; 145 } 146 } 147 148 return -1; 149} 150 151static void ftp_free(struct inode *inode) 152{ 153 struct pxe_pvt_inode *socket = PVT(inode); 154 155 if (socket->ctl) { 156 core_tcp_close_file(socket->ctl); 157 free_socket(socket->ctl); 158 socket->ctl = NULL; 159 } 160 core_tcp_close_file(inode); 161} 162 163static void ftp_close_file(struct inode *inode) 164{ 165 struct pxe_pvt_inode *socket = PVT(inode); 166 struct pxe_pvt_inode *ctlsock; 167 int resp; 168 169 ctlsock = socket->ctl ? PVT(socket->ctl) : NULL; 170 if (core_tcp_is_connected(ctlsock)) { 171 resp = ftp_cmd_response(socket->ctl, "QUIT", NULL, NULL, NULL); 172 while (resp == 226) { 173 resp = ftp_cmd_response(socket->ctl, NULL, NULL, NULL, NULL); 174 } 175 } 176 ftp_free(inode); 177} 178 179static const struct pxe_conn_ops ftp_conn_ops = { 180 .fill_buffer = core_tcp_fill_buffer, 181 .close = ftp_close_file, 182 .readdir = ftp_readdir, 183}; 184 185void ftp_open(struct url_info *url, int flags, struct inode *inode, 186 const char **redir) 187{ 188 struct pxe_pvt_inode *socket = PVT(inode); 189 struct pxe_pvt_inode *ctlsock; 190 uint8_t pasv_data[6]; 191 int pasv_bytes; 192 int resp; 193 err_t err; 194 195 (void)redir; /* FTP does not redirect */ 196 197 inode->size = 0; 198 199 if (!url->port) 200 url->port = 21; 201 202 url_unescape(url->path, 0); 203 204 socket->ops = &ftp_conn_ops; 205 206 /* Set up the control connection */ 207 socket->ctl = alloc_inode(inode->fs, 0, sizeof(struct pxe_pvt_inode)); 208 if (!socket->ctl) 209 return; 210 ctlsock = PVT(socket->ctl); 211 ctlsock->ops = &tcp_conn_ops; /* The control connection is just TCP */ 212 if (core_tcp_open(ctlsock)) 213 goto err_free; 214 err = core_tcp_connect(ctlsock, url->ip, url->port); 215 if (err) 216 goto err_delete; 217 218 do { 219 resp = ftp_cmd_response(socket->ctl, NULL, NULL, NULL, NULL); 220 } while (resp == 120); 221 if (resp != 220) 222 goto err_disconnect; 223 224 if (!url->user) 225 url->user = "anonymous"; 226 if (!url->passwd) 227 url->passwd = "syslinux@"; 228 229 resp = ftp_cmd_response(socket->ctl, "USER", url->user, NULL, NULL); 230 if (resp != 202 && resp != 230) { 231 if (resp != 331) 232 goto err_disconnect; 233 234 resp = ftp_cmd_response(socket->ctl, "PASS", url->passwd, NULL, NULL); 235 if (resp != 230) 236 goto err_disconnect; 237 } 238 239 if (!(flags & O_DIRECTORY)) { 240 resp = ftp_cmd_response(socket->ctl, "TYPE", "I", NULL, NULL); 241 if (resp != 200) 242 goto err_disconnect; 243 } 244 245 resp = ftp_cmd_response(socket->ctl, "PASV", NULL, pasv_data, &pasv_bytes); 246 if (resp != 227 || pasv_bytes != 6) 247 goto err_disconnect; 248 249 err = core_tcp_open(socket); 250 if (err) 251 goto err_disconnect; 252 err = core_tcp_connect(socket, *(uint32_t*)&pasv_data[0], 253 ntohs(*(uint16_t *)&pasv_data[4])); 254 if (err) 255 goto err_disconnect; 256 257 resp = ftp_cmd_response(socket->ctl, 258 (flags & O_DIRECTORY) ? "LIST" : "RETR", 259 url->path, NULL, NULL); 260 if (resp != 125 && resp != 150) 261 goto err_disconnect; 262 263 inode->size = -1; 264 return; /* Sucess! */ 265 266err_disconnect: 267 core_tcp_write(ctlsock, "QUIT\r\n", 6, false); 268 core_tcp_close_file(inode); 269err_delete: 270 core_tcp_close_file(socket->ctl); 271err_free: 272 free_socket(socket->ctl); 273} 274