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
ftp_cmd_response(struct inode * inode,const char * cmd,const char * cmd_arg,uint8_t * pasv_data,int * pn_ptr)31 static 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
ftp_free(struct inode * inode)151 static 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
ftp_close_file(struct inode * inode)163 static 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
179 static 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
ftp_open(struct url_info * url,int flags,struct inode * inode,const char ** redir)185 void 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
266 err_disconnect:
267 core_tcp_write(ctlsock, "QUIT\r\n", 6, false);
268 core_tcp_close_file(inode);
269 err_delete:
270 core_tcp_close_file(socket->ctl);
271 err_free:
272 free_socket(socket->ctl);
273 }
274