OLD | NEW |
(Empty) | |
| 1 /* |
| 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 <lib/minip.h> |
| 33 #include <lib/cksum.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 |
| 62 static 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. |
| 66 typedef 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 pkt_count; |
| 77 } tftp_job_t; |
| 78 |
| 79 uint16_t next_port = 2224; |
| 80 |
| 81 static void send_ack(udp_socket_t* socket, uint16_t count) |
| 82 { |
| 83 // Packet is [4][count]. |
| 84 status_t st; |
| 85 uint16_t ack[] = {htons(TFTP_OPCODE_ACK), htons(count)}; |
| 86 st = udp_send(ack, sizeof(ack), socket); |
| 87 if (st < 0) { |
| 88 LTRACEF("send_ack failed: %d\n", st); |
| 89 } |
| 90 } |
| 91 |
| 92 static void send_error(udp_socket_t* socket, uint16_t code) |
| 93 { |
| 94 // Packet is [5][error code][error in ascii-string][0]. |
| 95 status_t st; |
| 96 uint16_t ncode = htons(code); |
| 97 uint16_t err[] = {htons(TFTP_OPCODE_ERROR), ncode, |
| 98 0x7245, 0x2072, 0x3030 + ncode, 0 }; |
| 99 st = udp_send(err, sizeof(err), socket); |
| 100 if (st < 0) { |
| 101 LTRACEF("send_err failed: %d\n", st); |
| 102 } |
| 103 } |
| 104 |
| 105 static void end_transfer(tftp_job_t* job) |
| 106 { |
| 107 udp_close(job->socket); |
| 108 job->socket = NULL; |
| 109 job->src_addr = 0UL; |
| 110 job->callback(NULL, 0UL, job->arg); |
| 111 } |
| 112 |
| 113 static void udp_wrq_callback(void *data, size_t len, |
| 114 uint32_t srcaddr, uint16_t srcport, |
| 115 void *arg) |
| 116 { |
| 117 // Packet is [3][count][data]. All packets but the last have 512 |
| 118 // bytes of data, including zero data. |
| 119 char* data_c = data; |
| 120 tftp_job_t* job = arg; |
| 121 job->pkt_count++; |
| 122 |
| 123 if (len < 4) { |
| 124 // Not to spec. Ignore. |
| 125 return; |
| 126 } |
| 127 |
| 128 if (!job->socket) { |
| 129 // It is possible to have the client sent another packet |
| 130 // after we called end_transfer(). |
| 131 return; |
| 132 } |
| 133 |
| 134 if ((srcaddr != job->src_addr) || (srcport != job->src_port)) { |
| 135 LTRACEF("invalid source\n"); |
| 136 send_error(job->socket, TFTP_ERROR_UNKNOWN_XFER); |
| 137 end_transfer(job); |
| 138 return; |
| 139 } |
| 140 |
| 141 if (RD_U16(data_c) != htons(TFTP_OPCODE_DATA)) { |
| 142 LTRACEF("invalid opcode\n"); |
| 143 send_error(job->socket, TFTP_ERROR_ILLEGAL_OP); |
| 144 end_transfer(job); |
| 145 return; |
| 146 } |
| 147 |
| 148 send_ack(job->socket, job->pkt_count); |
| 149 |
| 150 if (job->callback(&data_c[4], len - 4, job->arg) < 0) { |
| 151 // The client wants to abort. |
| 152 send_error(job->socket, TFTP_ERROR_FULL); |
| 153 end_transfer(job); |
| 154 } |
| 155 |
| 156 // 512 bytes payload plus 4 of fixed header. The last packet. |
| 157 // has always less than 512 bytes of payload. |
| 158 if (len != 516) { |
| 159 end_transfer(job); |
| 160 } |
| 161 } |
| 162 |
| 163 static tftp_job_t* get_job_by_name(const char* file_name) |
| 164 { |
| 165 DEBUG_ASSERT(file_name); |
| 166 tftp_job_t *entry; |
| 167 list_for_every_entry(&tftp_list, entry, tftp_job_t, list) { |
| 168 if (strcmp(entry->file_name, file_name) == 0) { |
| 169 return entry; |
| 170 } |
| 171 } |
| 172 return NULL; |
| 173 } |
| 174 |
| 175 static void udp_svc_callback(void *data, size_t len, |
| 176 uint32_t srcaddr, uint16_t srcport, |
| 177 void *arg) |
| 178 { |
| 179 status_t st; |
| 180 uint16_t opcode; |
| 181 udp_socket_t* socket; |
| 182 tftp_job_t* job; |
| 183 |
| 184 st = udp_open(srcaddr, next_port, srcport, &socket); |
| 185 if (st < 0) { |
| 186 LTRACEF("error opening send socket %d\n", st); |
| 187 return; |
| 188 } |
| 189 |
| 190 opcode = ntohs(RD_U16(data)); |
| 191 |
| 192 if (opcode != TFTP_OPCODE_WRQ) |
| 193 { |
| 194 // Operation not suported. |
| 195 LTRACEF("op not supported, opcode: %d\n", opcode); |
| 196 send_error(socket, TFTP_ERROR_ACCESS); |
| 197 udp_close(socket); |
| 198 return; |
| 199 } |
| 200 |
| 201 // Look for a client that can hadle the file. TODO: |data| |
| 202 // needs to be null terminated. A malicious client can crash us. |
| 203 job = get_job_by_name(((char*)data) + 2); |
| 204 |
| 205 if (!job) { |
| 206 // Nobody claims to handle that file. |
| 207 LTRACEF("no client registered for file\n"); |
| 208 send_error(socket, TFTP_ERROR_UNKNOWN_XFER); |
| 209 udp_close(socket); |
| 210 return; |
| 211 } |
| 212 |
| 213 if (job->socket) { |
| 214 // There is already an ongoing job. |
| 215 // TODO: garbage collect the existing one if too long since the |
| 216 // last packet was processed. |
| 217 LTRACEF("existing job in progress\n"); |
| 218 send_error(socket, TFTP_ERROR_EXISTS); |
| 219 udp_close(socket); |
| 220 return; |
| 221 } |
| 222 |
| 223 LTRACEF("write op accepted, port %d\n", srcport); |
| 224 // Request accepted. The rest of the transfer happens between |
| 225 // next_port <----> srcport via udp_wrq_callback(). |
| 226 |
| 227 job->socket = socket; |
| 228 job->src_addr = srcaddr; |
| 229 job->src_port = srcport; |
| 230 job->pkt_count = 0UL; |
| 231 |
| 232 st = udp_listen(next_port, &udp_wrq_callback, job); |
| 233 if (st < 0) { |
| 234 LTRACEF("error listening on port\n"); |
| 235 return; |
| 236 } |
| 237 |
| 238 send_ack(socket, 0UL); |
| 239 next_port++; |
| 240 } |
| 241 |
| 242 int tftp_set_write_client(const char* file_name, tftp_callback_t cb, void* arg) |
| 243 { |
| 244 DEBUG_ASSERT(file_name); |
| 245 DEBUG_ASSERT(cb); |
| 246 |
| 247 tftp_job_t *job; |
| 248 |
| 249 list_for_every_entry(&tftp_list, job, tftp_job_t, list) { |
| 250 if (strcmp(file_name, job->file_name) == 0) { |
| 251 // TODO: un-registration. |
| 252 return -1; |
| 253 } |
| 254 } |
| 255 |
| 256 if ((job = malloc(sizeof(tftp_job_t))) == NULL) { |
| 257 return -1; |
| 258 } |
| 259 |
| 260 memset(job, 0, sizeof(tftp_job_t)); |
| 261 job->file_name = file_name; |
| 262 job->callback = cb; |
| 263 job->arg = arg; |
| 264 |
| 265 list_add_tail(&tftp_list, &job->list); |
| 266 return 0; |
| 267 } |
| 268 |
| 269 static unsigned long test_crc = 0UL; |
| 270 |
| 271 int test_tftp_client(void* data, size_t len, void* arg) { |
| 272 if (!data) { |
| 273 printf("--test transfer done-- crc32 = %lu\n", test_crc); |
| 274 test_crc = 0UL; |
| 275 } |
| 276 |
| 277 test_crc = crc32(test_crc, data,len); |
| 278 return 0; |
| 279 } |
| 280 |
| 281 int tftp_server_init(void *arg) |
| 282 { |
| 283 tftp_set_write_client("tftp_test.txt", &test_tftp_client, NULL); |
| 284 |
| 285 status_t st = udp_listen(TFTP_PORT, &udp_svc_callback, 0); |
| 286 return st; |
| 287 } |
| 288 |
OLD | NEW |