BadVPN – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /** |
2 | * |
||
3 | * @file tftp_server.c |
||
4 | * |
||
5 | * @author Logan Gunthorpe <logang@deltatee.com> |
||
6 | * Dirk Ziegelmeier <dziegel@gmx.de> |
||
7 | * |
||
8 | * @brief Trivial File Transfer Protocol (RFC 1350) |
||
9 | * |
||
10 | * Copyright (c) Deltatee Enterprises Ltd. 2013 |
||
11 | * All rights reserved. |
||
12 | * |
||
13 | */ |
||
14 | |||
15 | /* |
||
16 | * Redistribution and use in source and binary forms, with or without |
||
17 | * modification,are permitted provided that the following conditions are met: |
||
18 | * |
||
19 | * 1. Redistributions of source code must retain the above copyright notice, |
||
20 | * this list of conditions and the following disclaimer. |
||
21 | * 2. Redistributions in binary form must reproduce the above copyright notice, |
||
22 | * this list of conditions and the following disclaimer in the documentation |
||
23 | * and/or other materials provided with the distribution. |
||
24 | * 3. The name of the author may not be used to endorse or promote products |
||
25 | * derived from this software without specific prior written permission. |
||
26 | * |
||
27 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
||
28 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
||
29 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
||
30 | * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||
31 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
||
32 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||
33 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||
34 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||
35 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||
36 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
37 | * |
||
38 | * Author: Logan Gunthorpe <logang@deltatee.com> |
||
39 | * Dirk Ziegelmeier <dziegel@gmx.de> |
||
40 | * |
||
41 | */ |
||
42 | |||
43 | /** |
||
44 | * @defgroup tftp TFTP server |
||
45 | * @ingroup apps |
||
46 | * |
||
47 | * This is simple TFTP server for the lwIP raw API. |
||
48 | */ |
||
49 | |||
50 | #include "lwip/apps/tftp_server.h" |
||
51 | |||
52 | #if LWIP_UDP |
||
53 | |||
54 | #include "lwip/udp.h" |
||
55 | #include "lwip/timeouts.h" |
||
56 | #include "lwip/debug.h" |
||
57 | |||
58 | #define TFTP_MAX_PAYLOAD_SIZE 512 |
||
59 | #define TFTP_HEADER_LENGTH 4 |
||
60 | |||
61 | #define TFTP_RRQ 1 |
||
62 | #define TFTP_WRQ 2 |
||
63 | #define TFTP_DATA 3 |
||
64 | #define TFTP_ACK 4 |
||
65 | #define TFTP_ERROR 5 |
||
66 | |||
67 | enum tftp_error { |
||
68 | TFTP_ERROR_FILE_NOT_FOUND = 1, |
||
69 | TFTP_ERROR_ACCESS_VIOLATION = 2, |
||
70 | TFTP_ERROR_DISK_FULL = 3, |
||
71 | TFTP_ERROR_ILLEGAL_OPERATION = 4, |
||
72 | TFTP_ERROR_UNKNOWN_TRFR_ID = 5, |
||
73 | TFTP_ERROR_FILE_EXISTS = 6, |
||
74 | TFTP_ERROR_NO_SUCH_USER = 7 |
||
75 | }; |
||
76 | |||
77 | #include <string.h> |
||
78 | |||
79 | struct tftp_state { |
||
80 | const struct tftp_context *ctx; |
||
81 | void *handle; |
||
82 | struct pbuf *last_data; |
||
83 | struct udp_pcb *upcb; |
||
84 | ip_addr_t addr; |
||
85 | u16_t port; |
||
86 | int timer; |
||
87 | int last_pkt; |
||
88 | u16_t blknum; |
||
89 | u8_t retries; |
||
90 | u8_t mode_write; |
||
91 | }; |
||
92 | |||
93 | static struct tftp_state tftp_state; |
||
94 | |||
95 | static void tftp_tmr(void *arg); |
||
96 | |||
97 | static void |
||
98 | close_handle(void) |
||
99 | { |
||
100 | tftp_state.port = 0; |
||
101 | ip_addr_set_any(0, &tftp_state.addr); |
||
102 | |||
103 | if (tftp_state.last_data != NULL) { |
||
104 | pbuf_free(tftp_state.last_data); |
||
105 | tftp_state.last_data = NULL; |
||
106 | } |
||
107 | |||
108 | sys_untimeout(tftp_tmr, NULL); |
||
109 | |||
110 | if (tftp_state.handle) { |
||
111 | tftp_state.ctx->close(tftp_state.handle); |
||
112 | tftp_state.handle = NULL; |
||
113 | LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: closing\n")); |
||
114 | } |
||
115 | } |
||
116 | |||
117 | static void |
||
118 | send_error(const ip_addr_t *addr, u16_t port, enum tftp_error code, const char *str) |
||
119 | { |
||
120 | int str_length = strlen(str); |
||
121 | struct pbuf *p; |
||
122 | u16_t *payload; |
||
123 | |||
124 | p = pbuf_alloc(PBUF_TRANSPORT, (u16_t)(TFTP_HEADER_LENGTH + str_length + 1), PBUF_RAM); |
||
125 | if (p == NULL) { |
||
126 | return; |
||
127 | } |
||
128 | |||
129 | payload = (u16_t *) p->payload; |
||
130 | payload[0] = PP_HTONS(TFTP_ERROR); |
||
131 | payload[1] = lwip_htons(code); |
||
132 | MEMCPY(&payload[2], str, str_length + 1); |
||
133 | |||
134 | udp_sendto(tftp_state.upcb, p, addr, port); |
||
135 | pbuf_free(p); |
||
136 | } |
||
137 | |||
138 | static void |
||
139 | send_ack(u16_t blknum) |
||
140 | { |
||
141 | struct pbuf *p; |
||
142 | u16_t *payload; |
||
143 | |||
144 | p = pbuf_alloc(PBUF_TRANSPORT, TFTP_HEADER_LENGTH, PBUF_RAM); |
||
145 | if (p == NULL) { |
||
146 | return; |
||
147 | } |
||
148 | payload = (u16_t *) p->payload; |
||
149 | |||
150 | payload[0] = PP_HTONS(TFTP_ACK); |
||
151 | payload[1] = lwip_htons(blknum); |
||
152 | udp_sendto(tftp_state.upcb, p, &tftp_state.addr, tftp_state.port); |
||
153 | pbuf_free(p); |
||
154 | } |
||
155 | |||
156 | static void |
||
157 | resend_data(void) |
||
158 | { |
||
159 | struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, tftp_state.last_data->len, PBUF_RAM); |
||
160 | if (p == NULL) { |
||
161 | return; |
||
162 | } |
||
163 | |||
164 | if (pbuf_copy(p, tftp_state.last_data) != ERR_OK) { |
||
165 | pbuf_free(p); |
||
166 | return; |
||
167 | } |
||
168 | |||
169 | udp_sendto(tftp_state.upcb, p, &tftp_state.addr, tftp_state.port); |
||
170 | pbuf_free(p); |
||
171 | } |
||
172 | |||
173 | static void |
||
174 | send_data(void) |
||
175 | { |
||
176 | u16_t *payload; |
||
177 | int ret; |
||
178 | |||
179 | if (tftp_state.last_data != NULL) { |
||
180 | pbuf_free(tftp_state.last_data); |
||
181 | } |
||
182 | |||
183 | tftp_state.last_data = pbuf_alloc(PBUF_TRANSPORT, TFTP_HEADER_LENGTH + TFTP_MAX_PAYLOAD_SIZE, PBUF_RAM); |
||
184 | if (tftp_state.last_data == NULL) { |
||
185 | return; |
||
186 | } |
||
187 | |||
188 | payload = (u16_t *) tftp_state.last_data->payload; |
||
189 | payload[0] = PP_HTONS(TFTP_DATA); |
||
190 | payload[1] = lwip_htons(tftp_state.blknum); |
||
191 | |||
192 | ret = tftp_state.ctx->read(tftp_state.handle, &payload[2], TFTP_MAX_PAYLOAD_SIZE); |
||
193 | if (ret < 0) { |
||
194 | send_error(&tftp_state.addr, tftp_state.port, TFTP_ERROR_ACCESS_VIOLATION, "Error occured while reading the file."); |
||
195 | close_handle(); |
||
196 | return; |
||
197 | } |
||
198 | |||
199 | pbuf_realloc(tftp_state.last_data, (u16_t)(TFTP_HEADER_LENGTH + ret)); |
||
200 | resend_data(); |
||
201 | } |
||
202 | |||
203 | static void |
||
204 | recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) |
||
205 | { |
||
206 | u16_t *sbuf = (u16_t *) p->payload; |
||
207 | int opcode; |
||
208 | |||
209 | LWIP_UNUSED_ARG(arg); |
||
210 | LWIP_UNUSED_ARG(upcb); |
||
211 | |||
212 | if (((tftp_state.port != 0) && (port != tftp_state.port)) || |
||
213 | (!ip_addr_isany_val(tftp_state.addr) && !ip_addr_cmp(&tftp_state.addr, addr))) { |
||
214 | send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Only one connection at a time is supported"); |
||
215 | pbuf_free(p); |
||
216 | return; |
||
217 | } |
||
218 | |||
219 | opcode = sbuf[0]; |
||
220 | |||
221 | tftp_state.last_pkt = tftp_state.timer; |
||
222 | tftp_state.retries = 0; |
||
223 | |||
224 | switch (opcode) { |
||
225 | case PP_HTONS(TFTP_RRQ): /* fall through */ |
||
226 | case PP_HTONS(TFTP_WRQ): { |
||
227 | const char tftp_null = 0; |
||
228 | char filename[TFTP_MAX_FILENAME_LEN + 1]; |
||
229 | char mode[TFTP_MAX_MODE_LEN + 1]; |
||
230 | u16_t filename_end_offset; |
||
231 | u16_t mode_end_offset; |
||
232 | |||
233 | if (tftp_state.handle != NULL) { |
||
234 | send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Only one connection at a time is supported"); |
||
235 | break; |
||
236 | } |
||
237 | |||
238 | sys_timeout(TFTP_TIMER_MSECS, tftp_tmr, NULL); |
||
239 | |||
240 | /* find \0 in pbuf -> end of filename string */ |
||
241 | filename_end_offset = pbuf_memfind(p, &tftp_null, sizeof(tftp_null), 2); |
||
242 | if ((u16_t)(filename_end_offset - 1) > sizeof(filename)) { |
||
243 | send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Filename too long/not NULL terminated"); |
||
244 | break; |
||
245 | } |
||
246 | pbuf_copy_partial(p, filename, filename_end_offset - 1, 2); |
||
247 | |||
248 | /* find \0 in pbuf -> end of mode string */ |
||
249 | mode_end_offset = pbuf_memfind(p, &tftp_null, sizeof(tftp_null), filename_end_offset + 1); |
||
250 | if ((u16_t)(mode_end_offset - filename_end_offset) > sizeof(mode)) { |
||
251 | send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Mode too long/not NULL terminated"); |
||
252 | break; |
||
253 | } |
||
254 | pbuf_copy_partial(p, mode, mode_end_offset - filename_end_offset, filename_end_offset + 1); |
||
255 | |||
256 | tftp_state.handle = tftp_state.ctx->open(filename, mode, opcode == PP_HTONS(TFTP_WRQ)); |
||
257 | tftp_state.blknum = 1; |
||
258 | |||
259 | if (!tftp_state.handle) { |
||
260 | send_error(addr, port, TFTP_ERROR_FILE_NOT_FOUND, "Unable to open requested file."); |
||
261 | break; |
||
262 | } |
||
263 | |||
264 | LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: %s request from ", (opcode == PP_HTONS(TFTP_WRQ)) ? "write" : "read")); |
||
265 | ip_addr_debug_print(TFTP_DEBUG | LWIP_DBG_STATE, addr); |
||
266 | LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, (" for '%s' mode '%s'\n", filename, mode)); |
||
267 | |||
268 | ip_addr_copy(tftp_state.addr, *addr); |
||
269 | tftp_state.port = port; |
||
270 | |||
271 | if (opcode == PP_HTONS(TFTP_WRQ)) { |
||
272 | tftp_state.mode_write = 1; |
||
273 | send_ack(0); |
||
274 | } else { |
||
275 | tftp_state.mode_write = 0; |
||
276 | send_data(); |
||
277 | } |
||
278 | |||
279 | break; |
||
280 | } |
||
281 | |||
282 | case PP_HTONS(TFTP_DATA): { |
||
283 | int ret; |
||
284 | u16_t blknum; |
||
285 | |||
286 | if (tftp_state.handle == NULL) { |
||
287 | send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "No connection"); |
||
288 | break; |
||
289 | } |
||
290 | |||
291 | if (tftp_state.mode_write != 1) { |
||
292 | send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Not a write connection"); |
||
293 | break; |
||
294 | } |
||
295 | |||
296 | blknum = lwip_ntohs(sbuf[1]); |
||
297 | pbuf_remove_header(p, TFTP_HEADER_LENGTH); |
||
298 | |||
299 | ret = tftp_state.ctx->write(tftp_state.handle, p); |
||
300 | if (ret < 0) { |
||
301 | send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "error writing file"); |
||
302 | close_handle(); |
||
303 | } else { |
||
304 | send_ack(blknum); |
||
305 | } |
||
306 | |||
307 | if (p->tot_len < TFTP_MAX_PAYLOAD_SIZE) { |
||
308 | close_handle(); |
||
309 | } |
||
310 | break; |
||
311 | } |
||
312 | |||
313 | case PP_HTONS(TFTP_ACK): { |
||
314 | u16_t blknum; |
||
315 | int lastpkt; |
||
316 | |||
317 | if (tftp_state.handle == NULL) { |
||
318 | send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "No connection"); |
||
319 | break; |
||
320 | } |
||
321 | |||
322 | if (tftp_state.mode_write != 0) { |
||
323 | send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Not a read connection"); |
||
324 | break; |
||
325 | } |
||
326 | |||
327 | blknum = lwip_ntohs(sbuf[1]); |
||
328 | if (blknum != tftp_state.blknum) { |
||
329 | send_error(addr, port, TFTP_ERROR_UNKNOWN_TRFR_ID, "Wrong block number"); |
||
330 | break; |
||
331 | } |
||
332 | |||
333 | lastpkt = 0; |
||
334 | |||
335 | if (tftp_state.last_data != NULL) { |
||
336 | lastpkt = tftp_state.last_data->tot_len != (TFTP_MAX_PAYLOAD_SIZE + TFTP_HEADER_LENGTH); |
||
337 | } |
||
338 | |||
339 | if (!lastpkt) { |
||
340 | tftp_state.blknum++; |
||
341 | send_data(); |
||
342 | } else { |
||
343 | close_handle(); |
||
344 | } |
||
345 | |||
346 | break; |
||
347 | } |
||
348 | |||
349 | default: |
||
350 | send_error(addr, port, TFTP_ERROR_ILLEGAL_OPERATION, "Unknown operation"); |
||
351 | break; |
||
352 | } |
||
353 | |||
354 | pbuf_free(p); |
||
355 | } |
||
356 | |||
357 | static void |
||
358 | tftp_tmr(void *arg) |
||
359 | { |
||
360 | LWIP_UNUSED_ARG(arg); |
||
361 | |||
362 | tftp_state.timer++; |
||
363 | |||
364 | if (tftp_state.handle == NULL) { |
||
365 | return; |
||
366 | } |
||
367 | |||
368 | sys_timeout(TFTP_TIMER_MSECS, tftp_tmr, NULL); |
||
369 | |||
370 | if ((tftp_state.timer - tftp_state.last_pkt) > (TFTP_TIMEOUT_MSECS / TFTP_TIMER_MSECS)) { |
||
371 | if ((tftp_state.last_data != NULL) && (tftp_state.retries < TFTP_MAX_RETRIES)) { |
||
372 | LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: timeout, retrying\n")); |
||
373 | resend_data(); |
||
374 | tftp_state.retries++; |
||
375 | } else { |
||
376 | LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: timeout\n")); |
||
377 | close_handle(); |
||
378 | } |
||
379 | } |
||
380 | } |
||
381 | |||
382 | /** @ingroup tftp |
||
383 | * Initialize TFTP server. |
||
384 | * @param ctx TFTP callback struct |
||
385 | */ |
||
386 | err_t |
||
387 | tftp_init(const struct tftp_context *ctx) |
||
388 | { |
||
389 | err_t ret; |
||
390 | |||
391 | struct udp_pcb *pcb = udp_new_ip_type(IPADDR_TYPE_ANY); |
||
392 | if (pcb == NULL) { |
||
393 | return ERR_MEM; |
||
394 | } |
||
395 | |||
396 | ret = udp_bind(pcb, IP_ANY_TYPE, TFTP_PORT); |
||
397 | if (ret != ERR_OK) { |
||
398 | udp_remove(pcb); |
||
399 | return ret; |
||
400 | } |
||
401 | |||
402 | tftp_state.handle = NULL; |
||
403 | tftp_state.port = 0; |
||
404 | tftp_state.ctx = ctx; |
||
405 | tftp_state.timer = 0; |
||
406 | tftp_state.last_data = NULL; |
||
407 | tftp_state.upcb = pcb; |
||
408 | |||
409 | udp_recv(pcb, recv, NULL); |
||
410 | |||
411 | return ERR_OK; |
||
412 | } |
||
413 | |||
414 | #endif /* LWIP_UDP */ |