blob: 78ade7964efc549e20ce3013e96f3c91e2f960c7 [file] [log] [blame]
rjw1f884582022-01-06 17:20:42 +08001/*
2 * Copyright (c) 2015 Carlos Pizano-Uribe <cpu@chromium.org>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files
6 * (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24#include <err.h>
25#include <trace.h>
26#include <assert.h>
27#include <stdlib.h>
28#include <string.h>
29#include <list.h>
30#include <compiler.h>
31#include <endian.h>
32#include <stdbool.h>
33#include <lib/minip.h>
34#include <platform.h>
35
36#include <lib/tftp.h>
37
38#define LOCAL_TRACE 0
39
40// TFTP Opcodes:
41#define TFTP_OPCODE_RRQ 1UL
42#define TFTP_OPCODE_WRQ 2UL
43#define TFTP_OPCODE_DATA 3UL
44#define TFTP_OPCODE_ACK 4UL
45#define TFTP_OPCODE_ERROR 5UL
46
47// TFTP Errors:
48#define TFTP_ERROR_UNDEF 0UL
49#define TFTP_ERROR_NOT_FOUND 1UL
50#define TFTP_ERROR_ACCESS 2UL
51#define TFTP_ERROR_FULL 3UL
52#define TFTP_ERROR_ILLEGAL_OP 4UL
53#define TFTP_ERROR_UNKNOWN_XFER 4UL
54#define TFTP_ERROR_EXISTS 6UL
55#define TFTP_ERROR_NO_SUCH_USER 7UL
56
57#define TFTP_PORT 69
58
59#define RD_U16(ptr) \
60 (uint16_t)(((uint16_t)*((uint8_t*)(ptr)+1)<<8)|(uint16_t)*(uint8_t*)(ptr))
61
62static struct list_node tftp_list = LIST_INITIAL_VALUE(tftp_list);
63
64// Represents tftp jobs and clients of them. If |socket| is not null the
65// job is in progress and members below it are valid.
66typedef struct {
67 struct list_node list;
68 // Registration info.
69 const char* file_name;
70 tftp_callback_t callback;
71 void *arg;
72 // Current job info.
73 udp_socket_t* socket;
74 uint32_t src_addr;
75 uint16_t src_port;
76 uint16_t listen_port;
77 uint16_t pkt_count;
78} tftp_job_t;
79
80uint16_t next_port = 2224;
81
82static void send_ack(udp_socket_t* socket, uint16_t count)
83{
84 // Packet is [4][count].
85 status_t st;
86 uint16_t ack[] = {htons(TFTP_OPCODE_ACK), htons(count)};
87 st = udp_send(ack, sizeof(ack), socket);
88 if (st < 0) {
89 LTRACEF("send_ack failed: %d\n", st);
90 }
91}
92
93static void send_error(udp_socket_t* socket, uint16_t code)
94{
95 // Packet is [5][error code][error in ascii-string][0].
96 status_t st;
97 uint16_t ncode = htons(code);
98 uint16_t err[] = { htons(TFTP_OPCODE_ERROR), ncode,
99 0x7245, 0x2072, 0x3030 + ncode, 0 };
100 st = udp_send(err, sizeof(err), socket);
101 if (st < 0) {
102 LTRACEF("send_err failed: %d\n", st);
103 }
104}
105
106static void end_transfer(tftp_job_t* job, bool do_callback)
107{
108 udp_listen(job->listen_port, NULL, NULL);
109 udp_close(job->socket);
110 job->socket = NULL;
111 job->src_addr = 0UL;
112 if (do_callback) {
113 job->callback(NULL, 0UL, job->arg);
114 }
115}
116
117static void udp_wrq_callback(void *data, size_t len,
118 uint32_t srcaddr, uint16_t srcport,
119 void *arg)
120{
121 // Packet is [3][count][data]. All packets but the last have 512
122 // bytes of data, including zero data.
123 char* data_c = data;
124 tftp_job_t* job = arg;
125 job->pkt_count++;
126
127 if (len < 4) {
128 // Not to spec. Ignore.
129 return;
130 }
131
132 if (!job->socket) {
133 // It is possible to have the client sent another packet
134 // after we called end_transfer().
135 return;
136 }
137
138 if ((srcaddr != job->src_addr) || (srcport != job->src_port)) {
139 LTRACEF("invalid source\n");
140 send_error(job->socket, TFTP_ERROR_UNKNOWN_XFER);
141 end_transfer(job, true);
142 return;
143 }
144
145 if (RD_U16(data_c) != htons(TFTP_OPCODE_DATA)) {
146 LTRACEF("invalid opcode\n");
147 send_error(job->socket, TFTP_ERROR_ILLEGAL_OP);
148 end_transfer(job, true);
149 return;
150 }
151
152 send_ack(job->socket, job->pkt_count);
153
154 if (job->callback(&data_c[4], len - 4, job->arg) < 0) {
155 // The client wants to abort.
156 send_error(job->socket, TFTP_ERROR_FULL);
157 end_transfer(job, true);
158 }
159
160 // 512 bytes payload plus 4 of fixed header. The last packet.
161 // has always less than 512 bytes of payload.
162 if (len != 516) {
163 end_transfer(job, true);
164 }
165}
166
167static tftp_job_t* get_job_by_name(const char* file_name)
168{
169 DEBUG_ASSERT(file_name);
170 tftp_job_t *entry;
171 list_for_every_entry(&tftp_list, entry, tftp_job_t, list) {
172 if (strcmp(entry->file_name, file_name) == 0) {
173 return entry;
174 }
175 }
176 return NULL;
177}
178
179static void udp_svc_callback(void *data, size_t len,
180 uint32_t srcaddr, uint16_t srcport,
181 void *arg)
182{
183 status_t st;
184 uint16_t opcode;
185 udp_socket_t* socket;
186 tftp_job_t* job;
187
188 st = udp_open(srcaddr, next_port, srcport, &socket);
189 if (st < 0) {
190 LTRACEF("error opening send socket %d\n", st);
191 return;
192 }
193
194 opcode = ntohs(RD_U16(data));
195
196 if (opcode != TFTP_OPCODE_WRQ)
197 {
198 // Operation not suported.
199 LTRACEF("op not supported, opcode: %d\n", opcode);
200 send_error(socket, TFTP_ERROR_ACCESS);
201 udp_close(socket);
202 return;
203 }
204
205 // Look for a client that can hadle the file. TODO: |data|
206 // needs to be null terminated. A malicious client can crash us.
207 job = get_job_by_name(((char*)data) + 2);
208
209 if (!job) {
210 // Nobody claims to handle that file.
211 LTRACEF("no client registered for file\n");
212 send_error(socket, TFTP_ERROR_UNKNOWN_XFER);
213 udp_close(socket);
214 return;
215 }
216
217 if (job->socket) {
218 // There is already an ongoing job.
219 // TODO: garbage collect the existing one if too long since the
220 // last packet was processed.
221 LTRACEF("existing job in progress\n");
222 send_error(socket, TFTP_ERROR_EXISTS);
223 udp_close(socket);
224 return;
225 }
226
227 LTRACEF("write op accepted, port %d\n", srcport);
228 // Request accepted. The rest of the transfer happens between
229 // next_port <----> srcport via udp_wrq_callback().
230
231 job->socket = socket;
232 job->src_addr = srcaddr;
233 job->src_port = srcport;
234 job->pkt_count = 0UL;
235 job->listen_port = next_port;
236
237 st = udp_listen(job->listen_port, &udp_wrq_callback, job);
238 if (st < 0) {
239 LTRACEF("error listening on port\n");
240 return;
241 }
242
243 send_ack(socket, 0UL);
244 next_port++;
245}
246
247int tftp_set_write_client(const char* file_name, tftp_callback_t cb, void* arg)
248{
249 DEBUG_ASSERT(file_name);
250 DEBUG_ASSERT(cb);
251
252 tftp_job_t *job;
253
254 list_for_every_entry(&tftp_list, job, tftp_job_t, list) {
255 if (strcmp(file_name, job->file_name) == 0) {
256 list_delete(&job->list);
257 if (job->socket) {
258 // There is a job in progress. It will be cancelled silently.
259 end_transfer(job, false);
260 }
261 return 0;
262 }
263 }
264
265 if ((job = malloc(sizeof(tftp_job_t))) == NULL) {
266 return -1;
267 }
268
269 memset(job, 0, sizeof(tftp_job_t));
270 job->file_name = file_name;
271 job->callback = cb;
272 job->arg = arg;
273
274 list_add_tail(&tftp_list, &job->list);
275 return 0;
276}
277
278int tftp_server_init(void *arg)
279{
280 status_t st = udp_listen(TFTP_PORT, &udp_svc_callback, 0);
281 return st;
282}
283