tftp.c 11.9 KB
Newer Older
1
/* SPDX-License-Identifier: MIT */
bellard's avatar
bellard committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
 * tftp.c - a simple, read-only tftp server for qemu
 *
 * Copyright (c) 2004 Magnus Damm <damm@opensource.se>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

26
#include "slirp.h"
27
28
29
30

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
bellard's avatar
bellard committed
31

32
33
34
35
static inline int tftp_session_in_use(struct tftp_session *spt)
{
    return (spt->slirp != NULL);
}
bellard's avatar
bellard committed
36

37
static inline void tftp_session_update(struct tftp_session *spt)
bellard's avatar
bellard committed
38
{
bellard's avatar
bellard committed
39
    spt->timestamp = curtime;
bellard's avatar
bellard committed
40
41
42
43
}

static void tftp_session_terminate(struct tftp_session *spt)
{
44
45
46
47
    if (spt->fd >= 0) {
        close(spt->fd);
        spt->fd = -1;
    }
48
    g_free(spt->filename);
49
    spt->slirp = NULL;
bellard's avatar
bellard committed
50
51
}

52
53
static int tftp_session_allocate(Slirp *slirp, struct sockaddr_storage *srcsas,
                                 struct tftp_t *tp)
bellard's avatar
bellard committed
54
55
56
57
58
{
    struct tftp_session *spt;
    int k;

    for (k = 0; k < TFTP_SESSIONS_MAX; k++) {
59
        spt = &slirp->tftp_sessions[k];
bellard's avatar
bellard committed
60

61
        if (!tftp_session_in_use(spt))
bellard's avatar
bellard committed
62
63
64
            goto found;

        /* sessions time out after 5 inactive seconds */
65
        if ((int)(curtime - spt->timestamp) > 5000) {
66
            tftp_session_terminate(spt);
bellard's avatar
bellard committed
67
            goto found;
68
        }
bellard's avatar
bellard committed
69
70
71
72
73
74
    }

    return -1;

found:
    memset(spt, 0, sizeof(*spt));
75
    memcpy(&spt->client_addr, srcsas, sockaddr_size(srcsas));
76
    spt->fd = -1;
77
    spt->block_size = 512;
bellard's avatar
bellard committed
78
    spt->client_port = tp->udp.uh_sport;
79
    spt->slirp = slirp;
bellard's avatar
bellard committed
80
81
82
83
84
85

    tftp_session_update(spt);

    return k;
}

86
87
static int tftp_session_find(Slirp *slirp, struct sockaddr_storage *srcsas,
                             struct tftp_t *tp)
bellard's avatar
bellard committed
88
89
90
91
92
{
    struct tftp_session *spt;
    int k;

    for (k = 0; k < TFTP_SESSIONS_MAX; k++) {
93
        spt = &slirp->tftp_sessions[k];
bellard's avatar
bellard committed
94

95
        if (tftp_session_in_use(spt)) {
96
            if (sockaddr_equal(&spt->client_addr, srcsas)) {
bellard's avatar
bellard committed
97
98
99
100
101
102
103
104
105
106
                if (spt->client_port == tp->udp.uh_sport) {
                    return k;
                }
            }
        }
    }

    return -1;
}

107
static int tftp_read_data(struct tftp_session *spt, uint32_t block_nr,
108
                          uint8_t *buf, int len)
bellard's avatar
bellard committed
109
110
{
    int bytes_read = 0;
111

112
113
114
    if (spt->fd < 0) {
        spt->fd = open(spt->filename, O_RDONLY | O_BINARY);
    }
bellard's avatar
bellard committed
115

116
    if (spt->fd < 0) {
bellard's avatar
bellard committed
117
118
119
120
        return -1;
    }

    if (len) {
Jindrich Novy's avatar
Jindrich Novy committed
121
122
123
        if (lseek(spt->fd, block_nr * spt->block_size, SEEK_SET) == (off_t)-1) {
            return -1;
        }
bellard's avatar
bellard committed
124

125
        bytes_read = read(spt->fd, buf, len);
bellard's avatar
bellard committed
126
127
128
129
130
    }

    return bytes_read;
}

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
static struct tftp_t *tftp_prep_mbuf_data(struct tftp_session *spt,
                                          struct mbuf *m)
{
    struct tftp_t *tp;

    memset(m->m_data, 0, m->m_size);

    m->m_data += IF_MAXLINKHDR;
    if (spt->client_addr.ss_family == AF_INET6) {
        m->m_data += sizeof(struct ip6);
    } else {
        m->m_data += sizeof(struct ip);
    }
    tp = (void *)m->m_data;
    m->m_data += sizeof(struct udphdr);

    return tp;
}

static void tftp_udp_output(struct tftp_session *spt, struct mbuf *m,
                            struct tftp_t *recv_tp)
{
    if (spt->client_addr.ss_family == AF_INET6) {
        struct sockaddr_in6 sa6, da6;

        sa6.sin6_addr = spt->slirp->vhost_addr6;
        sa6.sin6_port = recv_tp->udp.uh_dport;
        da6.sin6_addr = ((struct sockaddr_in6 *)&spt->client_addr)->sin6_addr;
        da6.sin6_port = spt->client_port;

        udp6_output(NULL, m, &sa6, &da6);
    } else {
        struct sockaddr_in sa4, da4;

        sa4.sin_addr = spt->slirp->vhost_addr;
        sa4.sin_port = recv_tp->udp.uh_dport;
        da4.sin_addr = ((struct sockaddr_in *)&spt->client_addr)->sin_addr;
        da4.sin_port = spt->client_port;

        udp_output(NULL, m, &sa4, &da4, IPTOS_LOWDELAY);
    }
}

174
175
static int tftp_send_oack(struct tftp_session *spt, const char *keys[],
                          uint32_t values[], int nb, struct tftp_t *recv_tp)
176
177
178
{
    struct mbuf *m;
    struct tftp_t *tp;
179
    int i, n = 0;
180

181
    m = m_get(spt->slirp);
182
183
184
185

    if (!m)
        return -1;

186
    tp = tftp_prep_mbuf_data(spt, m);
187
188

    tp->tp_op = htons(TFTP_OACK);
189
    for (i = 0; i < nb; i++) {
Marc-André Lureau's avatar
Marc-André Lureau committed
190
191
        n += slirp_fmt0(tp->x.tp_buf + n, sizeof(tp->x.tp_buf) - n, "%s", keys[i]);
        n += slirp_fmt0(tp->x.tp_buf + n, sizeof(tp->x.tp_buf) - n, "%u", values[i]);
192
    }
193

194
    m->m_len = G_SIZEOF_MEMBER(struct tftp_t, tp_op) + n;
195
    tftp_udp_output(spt, m, recv_tp);
196
197
198
199

    return 0;
}

200
static void tftp_send_error(struct tftp_session *spt, uint16_t errorcode,
201
                            const char *msg, struct tftp_t *recv_tp)
bellard's avatar
bellard committed
202
203
204
205
{
    struct mbuf *m;
    struct tftp_t *tp;

206
207
    DEBUG_TFTP("tftp error msg: %s", msg);

208
    m = m_get(spt->slirp);
bellard's avatar
bellard committed
209
210

    if (!m) {
211
        goto out;
bellard's avatar
bellard committed
212
213
    }

214
    tp = tftp_prep_mbuf_data(spt, m);
bellard's avatar
bellard committed
215
216
217

    tp->tp_op = htons(TFTP_ERROR);
    tp->x.tp_error.tp_error_code = htons(errorcode);
218
219
    slirp_pstrcpy((char *)tp->x.tp_error.tp_msg, sizeof(tp->x.tp_error.tp_msg),
                  msg);
bellard's avatar
bellard committed
220

221
222
    m->m_len = sizeof(struct tftp_t) - (TFTP_BLOCKSIZE_MAX + 2) + 3 +
               strlen(msg) - sizeof(struct udphdr);
223
    tftp_udp_output(spt, m, recv_tp);
bellard's avatar
bellard committed
224

225
out:
bellard's avatar
bellard committed
226
227
228
    tftp_session_terminate(spt);
}

229
230
static void tftp_send_next_block(struct tftp_session *spt,
                                 struct tftp_t *recv_tp)
bellard's avatar
bellard committed
231
232
233
234
235
{
    struct mbuf *m;
    struct tftp_t *tp;
    int nobytes;

236
    m = m_get(spt->slirp);
bellard's avatar
bellard committed
237
238

    if (!m) {
239
        return;
bellard's avatar
bellard committed
240
241
    }

242
    tp = tftp_prep_mbuf_data(spt, m);
bellard's avatar
bellard committed
243
244

    tp->tp_op = htons(TFTP_DATA);
245
    tp->x.tp_data.tp_block_nr = htons((spt->block_nr + 1) & 0xffff);
bellard's avatar
bellard committed
246

247
248
    nobytes = tftp_read_data(spt, spt->block_nr, tp->x.tp_data.tp_buf,
                             spt->block_size);
bellard's avatar
bellard committed
249
250
251
252
253
254
255
256

    if (nobytes < 0) {
        m_free(m);

        /* send "file not found" error back */

        tftp_send_error(spt, 1, "File not found", tp);

257
        return;
bellard's avatar
bellard committed
258
259
    }

260
261
    m->m_len = sizeof(struct tftp_t) - (TFTP_BLOCKSIZE_MAX - nobytes) -
               sizeof(struct udphdr);
262
    tftp_udp_output(spt, m, recv_tp);
bellard's avatar
bellard committed
263

264
    if (nobytes == spt->block_size) {
bellard's avatar
bellard committed
265
266
267
268
269
        tftp_session_update(spt);
    } else {
        tftp_session_terminate(spt);
    }

270
    spt->block_nr++;
bellard's avatar
bellard committed
271
272
}

273
274
static void tftp_handle_rrq(Slirp *slirp, struct sockaddr_storage *srcsas,
                            struct tftp_t *tp, int pktlen)
bellard's avatar
bellard committed
275
276
{
    struct tftp_session *spt;
277
    int s, k;
278
    size_t prefix_len;
279
    char *req_fname;
280
281
282
    const char *option_name[2];
    uint32_t option_value[2];
    int nb_options = 0;
bellard's avatar
bellard committed
283

Thomas Horsten's avatar
Thomas Horsten committed
284
    /* check if a session already exists and if so terminate it */
285
    s = tftp_session_find(slirp, srcsas, tp);
Thomas Horsten's avatar
Thomas Horsten committed
286
287
288
289
    if (s >= 0) {
        tftp_session_terminate(&slirp->tftp_sessions[s]);
    }

290
    s = tftp_session_allocate(slirp, srcsas, tp);
bellard's avatar
bellard committed
291
292
293
294
295

    if (s < 0) {
        return;
    }

296
    spt = &slirp->tftp_sessions[s];
bellard's avatar
bellard committed
297

Deepak Kathayat's avatar
Deepak Kathayat committed
298
    /* unspecified prefix means service disabled */
299
    if (!slirp->tftp_prefix) {
300
301
302
303
        tftp_send_error(spt, 2, "Access violation", tp);
        return;
    }

304
305
    /* skip header fields */
    k = 0;
306
    pktlen -= offsetof(struct tftp_t, x.tp_buf);
bellard's avatar
bellard committed
307

308
    /* prepend tftp_prefix */
309
    prefix_len = strlen(slirp->tftp_prefix);
310
    spt->filename = g_malloc(prefix_len + TFTP_FILENAME_MAX + 2);
311
    memcpy(spt->filename, slirp->tftp_prefix, prefix_len);
312
    spt->filename[prefix_len] = '/';
313

bellard's avatar
bellard committed
314
    /* get name */
315
    req_fname = spt->filename + prefix_len + 1;
bellard's avatar
bellard committed
316

317
318
319
    while (1) {
        if (k >= TFTP_FILENAME_MAX || k >= pktlen) {
            tftp_send_error(spt, 2, "Access violation", tp);
bellard's avatar
bellard committed
320
321
            return;
        }
322
        req_fname[k] = tp->x.tp_buf[k];
323
        if (req_fname[k++] == '\0') {
bellard's avatar
bellard committed
324
325
326
            break;
        }
    }
327
328

    DEBUG_TFTP("tftp rrq file: %s", req_fname);
bellard's avatar
bellard committed
329
330

    /* check mode */
331
332
    if ((pktlen - k) < 6) {
        tftp_send_error(spt, 2, "Access violation", tp);
bellard's avatar
bellard committed
333
334
335
        return;
    }

336
    if (strcasecmp(&tp->x.tp_buf[k], "octet") != 0) {
bellard's avatar
bellard committed
337
338
339
340
        tftp_send_error(spt, 4, "Unsupported transfer mode", tp);
        return;
    }

341
342
    k += 6; /* skipping octet */

bellard's avatar
bellard committed
343
    /* do sanity checks on the filename */
344
345
346
347
348
349
350
    if (
#ifdef G_OS_WIN32
        strstr(req_fname, "..\\") ||
        req_fname[strlen(req_fname) - 1] == '\\' ||
#endif
        strstr(req_fname, "../") ||
        req_fname[strlen(req_fname) - 1] == '/') {
bellard's avatar
bellard committed
351
352
353
354
355
        tftp_send_error(spt, 2, "Access violation", tp);
        return;
    }

    /* check if the file exists */
356
    if (tftp_read_data(spt, 0, NULL, 0) < 0) {
bellard's avatar
bellard committed
357
358
359
360
        tftp_send_error(spt, 1, "File not found", tp);
        return;
    }

361
    if (tp->x.tp_buf[pktlen - 1] != 0) {
362
363
364
365
        tftp_send_error(spt, 2, "Access violation", tp);
        return;
    }

366
    while (k < pktlen && nb_options < G_N_ELEMENTS(option_name)) {
367
368
        const char *key, *value;

369
        key = &tp->x.tp_buf[k];
370
371
        k += strlen(key) + 1;

372
        if (k >= pktlen) {
373
374
375
376
            tftp_send_error(spt, 2, "Access violation", tp);
            return;
        }

377
        value = &tp->x.tp_buf[k];
378
379
        k += strlen(value) + 1;

380
        if (strcasecmp(key, "tsize") == 0) {
381
382
383
            int tsize = atoi(value);
            struct stat stat_p;

384
            if (tsize == 0) {
385
                if (stat(spt->filename, &stat_p) == 0)
386
387
388
389
390
391
392
                    tsize = stat_p.st_size;
                else {
                    tftp_send_error(spt, 1, "File not found", tp);
                    return;
                }
            }

393
394
395
396
397
398
            option_name[nb_options] = "tsize";
            option_value[nb_options] = tsize;
            nb_options++;
        } else if (strcasecmp(key, "blksize") == 0) {
            int blksize = atoi(value);

399
400
401
            /* Accept blksize up to our maximum size */
            if (blksize > 0) {
                spt->block_size = MIN(blksize, TFTP_BLOCKSIZE_MAX);
402
                option_name[nb_options] = "blksize";
403
                option_value[nb_options] = spt->block_size;
404
405
                nb_options++;
            }
406
407
408
        }
    }

409
    if (nb_options > 0) {
410
        assert(nb_options <= G_N_ELEMENTS(option_name));
411
412
413
414
        tftp_send_oack(spt, option_name, option_value, nb_options, tp);
        return;
    }

415
416
    spt->block_nr = 0;
    tftp_send_next_block(spt, tp);
bellard's avatar
bellard committed
417
418
}

419
420
static void tftp_handle_ack(Slirp *slirp, struct sockaddr_storage *srcsas,
                            struct tftp_t *tp, int pktlen)
bellard's avatar
bellard committed
421
422
423
{
    int s;

424
    s = tftp_session_find(slirp, srcsas, tp);
bellard's avatar
bellard committed
425
426
427
428
429

    if (s < 0) {
        return;
    }

430
    tftp_send_next_block(&slirp->tftp_sessions[s], tp);
bellard's avatar
bellard committed
431
432
}

433
434
static void tftp_handle_error(Slirp *slirp, struct sockaddr_storage *srcsas,
                              struct tftp_t *tp, int pktlen)
Thomas Horsten's avatar
Thomas Horsten committed
435
436
437
{
    int s;

438
    s = tftp_session_find(slirp, srcsas, tp);
Thomas Horsten's avatar
Thomas Horsten committed
439
440
441
442
443
444
445
446

    if (s < 0) {
        return;
    }

    tftp_session_terminate(&slirp->tftp_sessions[s]);
}

447
void tftp_input(struct sockaddr_storage *srcsas, struct mbuf *m)
bellard's avatar
bellard committed
448
{
449
450
451
452
453
    struct tftp_t *tp = mtod_check(m, offsetof(struct tftp_t, x.tp_buf));

    if (tp == NULL) {
        return;
    }
bellard's avatar
bellard committed
454
455
456

    switch (ntohs(tp->tp_op)) {
    case TFTP_RRQ:
457
        tftp_handle_rrq(m->slirp, srcsas, tp, m->m_len);
bellard's avatar
bellard committed
458
459
460
        break;

    case TFTP_ACK:
461
        tftp_handle_ack(m->slirp, srcsas, tp, m->m_len);
bellard's avatar
bellard committed
462
        break;
Thomas Horsten's avatar
Thomas Horsten committed
463
464

    case TFTP_ERROR:
465
        tftp_handle_error(m->slirp, srcsas, tp, m->m_len);
Thomas Horsten's avatar
Thomas Horsten committed
466
        break;
bellard's avatar
bellard committed
467
468
    }
}