1 /*
2  * Copyright (c) 2015 Carlos Pizano-Uribe <cpu@chromium.org>
3  *
4  * Use of this source code is governed by a MIT-style
5  * license that can be found in the LICENSE file or at
6  * https://opensource.org/licenses/MIT
7  */
8 
9 #include <lk/err.h>
10 #include <lk/trace.h>
11 #include <assert.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <lk/list.h>
15 #include <lk/compiler.h>
16 #include <endian.h>
17 #include <stdbool.h>
18 #include <lib/minip.h>
19 #include <platform.h>
20 
21 #include <lib/tftp.h>
22 
23 #define LOCAL_TRACE 0
24 
25 // TFTP Opcodes:
26 #define TFTP_OPCODE_RRQ   1UL
27 #define TFTP_OPCODE_WRQ   2UL
28 #define TFTP_OPCODE_DATA  3UL
29 #define TFTP_OPCODE_ACK   4UL
30 #define TFTP_OPCODE_ERROR 5UL
31 
32 // TFTP Errors:
33 #define TFTP_ERROR_UNDEF        0UL
34 #define TFTP_ERROR_NOT_FOUND    1UL
35 #define TFTP_ERROR_ACCESS       2UL
36 #define TFTP_ERROR_FULL         3UL
37 #define TFTP_ERROR_ILLEGAL_OP   4UL
38 #define TFTP_ERROR_UNKNOWN_XFER 4UL
39 #define TFTP_ERROR_EXISTS       6UL
40 #define TFTP_ERROR_NO_SUCH_USER 7UL
41 
42 #define TFTP_PORT 69
43 
44 #define RD_U16(ptr) \
45     (uint16_t)(((uint16_t)*((uint8_t*)(ptr)+1)<<8)|(uint16_t)*(uint8_t*)(ptr))
46 
47 static struct list_node tftp_list = LIST_INITIAL_VALUE(tftp_list);
48 
49 // Represents tftp jobs and clients of them. If |socket| is not null the
50 // job is in progress and members below it are valid.
51 typedef struct {
52     struct list_node list;
53     // Registration info.
54     const char *file_name;
55     tftp_callback_t callback;
56     void *arg;
57     // Current job info.
58     udp_socket_t *socket;
59     uint32_t src_addr;
60     uint16_t src_port;
61     uint16_t listen_port;
62     uint16_t pkt_count;
63 } tftp_job_t;
64 
65 uint16_t next_port = 2224;
66 
send_ack(udp_socket_t * socket,uint16_t count)67 static void send_ack(udp_socket_t *socket, uint16_t count) {
68     // Packet is [4][count].
69     status_t st;
70     uint16_t ack[] = {htons(TFTP_OPCODE_ACK), htons(count)};
71     st = udp_send(ack, sizeof(ack), socket);
72     if (st < 0) {
73         LTRACEF("send_ack failed: %d\n", st);
74     }
75 }
76 
send_error(udp_socket_t * socket,uint16_t code)77 static void send_error(udp_socket_t *socket, uint16_t code) {
78     // Packet is [5][error code][error in ascii-string][0].
79     status_t st;
80     uint16_t ncode = htons(code);
81     uint16_t err[] = { htons(TFTP_OPCODE_ERROR), ncode,
82                        0x7245, 0x2072, 0x3030 + ncode, 0
83                      };
84     st = udp_send(err, sizeof(err), socket);
85     if (st < 0) {
86         LTRACEF("send_err failed: %d\n", st);
87     }
88 }
89 
end_transfer(tftp_job_t * job,bool do_callback)90 static void end_transfer(tftp_job_t *job, bool do_callback) {
91     udp_listen(job->listen_port, NULL, NULL);
92     udp_close(job->socket);
93     job->socket = NULL;
94     job->src_addr = 0UL;
95     if (do_callback) {
96         job->callback(NULL, 0UL, job->arg);
97     }
98 }
99 
udp_wrq_callback(void * data,size_t len,uint32_t srcaddr,uint16_t srcport,void * arg)100 static void udp_wrq_callback(void *data, size_t len,
101                              uint32_t srcaddr, uint16_t srcport,
102                              void *arg) {
103     // Packet is [3][count][data]. All packets but the last have 512
104     // bytes of data, including zero data.
105     char *data_c = data;
106     tftp_job_t *job = arg;
107     job->pkt_count++;
108 
109     if (len < 4) {
110         // Not to spec. Ignore.
111         return;
112     }
113 
114     if (!job->socket) {
115         // It is possible to have the client sent another packet
116         // after we called end_transfer().
117         return;
118     }
119 
120     if ((srcaddr != job->src_addr) || (srcport != job->src_port)) {
121         LTRACEF("invalid source\n");
122         send_error(job->socket, TFTP_ERROR_UNKNOWN_XFER);
123         end_transfer(job, true);
124         return;
125     }
126 
127     if (RD_U16(data_c) != htons(TFTP_OPCODE_DATA)) {
128         LTRACEF("invalid opcode\n");
129         send_error(job->socket, TFTP_ERROR_ILLEGAL_OP);
130         end_transfer(job, true);
131         return;
132     }
133 
134     send_ack(job->socket, job->pkt_count);
135 
136     if (job->callback(&data_c[4], len - 4, job->arg) < 0) {
137         // The client wants to abort.
138         send_error(job->socket, TFTP_ERROR_FULL);
139         end_transfer(job, true);
140     }
141 
142     // 512 bytes payload plus 4 of fixed header. The last packet.
143     // has always less than 512 bytes of payload.
144     if (len != 516) {
145         end_transfer(job, true);
146     }
147 }
148 
get_job_by_name(const char * file_name)149 static tftp_job_t *get_job_by_name(const char *file_name) {
150     DEBUG_ASSERT(file_name);
151     tftp_job_t *entry;
152     list_for_every_entry(&tftp_list, entry, tftp_job_t, list) {
153         if (strcmp(entry->file_name, file_name) == 0) {
154             return entry;
155         }
156     }
157     return NULL;
158 }
159 
udp_svc_callback(void * data,size_t len,uint32_t srcaddr,uint16_t srcport,void * arg)160 static void udp_svc_callback(void *data, size_t len,
161                              uint32_t srcaddr, uint16_t srcport,
162                              void *arg) {
163     status_t st;
164     uint16_t opcode;
165     udp_socket_t *socket;
166     tftp_job_t *job;
167 
168     st = udp_open(srcaddr, next_port, srcport, &socket);
169     if (st < 0) {
170         LTRACEF("error opening send socket %d\n", st);
171         return;
172     }
173 
174     opcode = ntohs(RD_U16(data));
175 
176     if (opcode != TFTP_OPCODE_WRQ) {
177         // Operation not supported.
178         LTRACEF("op not supported, opcode: %d\n", opcode);
179         send_error(socket, TFTP_ERROR_ACCESS);
180         udp_close(socket);
181         return;
182     }
183 
184     // Look for a client that can hadle the file. TODO: |data|
185     // needs to be null terminated. A malicious client can crash us.
186     job = get_job_by_name(((char *)data) + 2);
187 
188     if (!job) {
189         // Nobody claims to handle that file.
190         LTRACEF("no client registered for file\n");
191         send_error(socket, TFTP_ERROR_UNKNOWN_XFER);
192         udp_close(socket);
193         return;
194     }
195 
196     if (job->socket) {
197         // There is already an ongoing job.
198         // TODO: garbage collect the existing one if too long since the
199         // last packet was processed.
200         LTRACEF("existing job in progress\n");
201         send_error(socket, TFTP_ERROR_EXISTS);
202         udp_close(socket);
203         return;
204     }
205 
206     LTRACEF("write op accepted, port %d\n", srcport);
207     // Request accepted. The rest of the transfer happens between
208     // next_port <----> srcport via udp_wrq_callback().
209 
210     job->socket = socket;
211     job->src_addr = srcaddr;
212     job->src_port = srcport;
213     job->pkt_count = 0UL;
214     job->listen_port = next_port;
215 
216     st = udp_listen(job->listen_port, &udp_wrq_callback, job);
217     if (st < 0) {
218         LTRACEF("error listening on port\n");
219         return;
220     }
221 
222     send_ack(socket, 0UL);
223     next_port++;
224 }
225 
tftp_set_write_client(const char * file_name,tftp_callback_t cb,void * arg)226 int tftp_set_write_client(const char *file_name, tftp_callback_t cb, void *arg) {
227     DEBUG_ASSERT(file_name);
228     DEBUG_ASSERT(cb);
229 
230     tftp_job_t *job;
231 
232     list_for_every_entry(&tftp_list, job, tftp_job_t, list) {
233         if (strcmp(file_name, job->file_name) == 0) {
234             list_delete(&job->list);
235             if (job->socket) {
236                 // There is a job in progress. It will be cancelled silently.
237                 end_transfer(job, false);
238             }
239             return 0;
240         }
241     }
242 
243     if ((job = malloc(sizeof(tftp_job_t))) == NULL) {
244         return -1;
245     }
246 
247     memset(job, 0, sizeof(tftp_job_t));
248     job->file_name = file_name;
249     job->callback = cb;
250     job->arg = arg;
251 
252     list_add_tail(&tftp_list, &job->list);
253     return 0;
254 }
255 
tftp_server_init(void * arg)256 int tftp_server_init(void *arg) {
257     status_t st = udp_listen(TFTP_PORT, &udp_svc_callback, 0);
258     return st;
259 }
260 
261