blob: 5a80ceb25f2675f26e3a4a3825d5b405514f93e0 [file] [log] [blame]
Simon Kelley5aabfc72007-08-29 11:24:47 +01001/* dnsmasq is Copyright (c) 2000-2007 Simon Kelley
Simon Kelley832af0b2007-01-21 20:01:28 +00002
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; version 2 dated June, 1991.
6
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
11*/
12
13#include "dnsmasq.h"
14
15#ifdef HAVE_TFTP
16
Simon Kelley5aabfc72007-08-29 11:24:47 +010017static struct tftp_file *check_tftp_fileperm(ssize_t *len);
Simon Kelley832af0b2007-01-21 20:01:28 +000018static void free_transfer(struct tftp_transfer *transfer);
19static ssize_t tftp_err(int err, char *packet, char *mess, char *file);
Simon Kelley1b7ecd12007-02-05 14:57:57 +000020static ssize_t tftp_err_oops(char *packet, char *file);
Simon Kelley832af0b2007-01-21 20:01:28 +000021static ssize_t get_block(char *packet, struct tftp_transfer *transfer);
22static char *next(char **p, char *end);
23
24#define OP_RRQ 1
25#define OP_WRQ 2
26#define OP_DATA 3
27#define OP_ACK 4
28#define OP_ERR 5
29#define OP_OACK 6
30
31#define ERR_NOTDEF 0
32#define ERR_FNF 1
33#define ERR_PERM 2
34#define ERR_FULL 3
35#define ERR_ILL 4
36
Simon Kelley5aabfc72007-08-29 11:24:47 +010037void tftp_request(struct listener *listen, time_t now)
Simon Kelley832af0b2007-01-21 20:01:28 +000038{
39 ssize_t len;
40 char *packet = daemon->packet;
41 char *filename, *mode, *p, *end, *opt;
Simon Kelley832af0b2007-01-21 20:01:28 +000042 struct sockaddr_in addr, peer;
43 struct msghdr msg;
44 struct cmsghdr *cmptr;
45 struct iovec iov;
46 struct ifreq ifr;
47 int is_err = 1, if_index = 0;
48 struct iname *tmp;
Simon Kelley5aabfc72007-08-29 11:24:47 +010049 struct tftp_transfer *transfer;
Simon Kelley832af0b2007-01-21 20:01:28 +000050
51 union {
52 struct cmsghdr align; /* this ensures alignment */
53#ifdef HAVE_LINUX_NETWORK
54 char control[CMSG_SPACE(sizeof(struct in_pktinfo))];
55#else
56 char control[CMSG_SPACE(sizeof(struct sockaddr_dl))];
57#endif
58 } control_u;
59
60 msg.msg_controllen = sizeof(control_u);
61 msg.msg_control = control_u.control;
62 msg.msg_flags = 0;
63 msg.msg_name = &peer;
64 msg.msg_namelen = sizeof(peer);
65 msg.msg_iov = &iov;
66 msg.msg_iovlen = 1;
67
68 iov.iov_base = packet;
69 iov.iov_len = daemon->packet_buff_sz;
70
71 /* we overwrote the buffer... */
72 daemon->srv_save = NULL;
73
74 if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2)
75 return;
76
Simon Kelley1b7ecd12007-02-05 14:57:57 +000077 if (daemon->options & OPT_NOWILD)
78 addr = listen->iface->addr.in;
79 else
80 {
81 addr.sin_addr.s_addr = 0;
82
Simon Kelley832af0b2007-01-21 20:01:28 +000083#if defined(HAVE_LINUX_NETWORK)
Simon Kelley1b7ecd12007-02-05 14:57:57 +000084 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
85 if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO)
86 {
87 addr.sin_addr = ((struct in_pktinfo *)CMSG_DATA(cmptr))->ipi_spec_dst;
88 if_index = ((struct in_pktinfo *)CMSG_DATA(cmptr))->ipi_ifindex;
89 }
90 if (!(ifr.ifr_ifindex = if_index) ||
91 ioctl(listen->tftpfd, SIOCGIFNAME, &ifr) == -1)
92 return;
93
Simon Kelley832af0b2007-01-21 20:01:28 +000094#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
Simon Kelley1b7ecd12007-02-05 14:57:57 +000095 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
96 if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR)
97 addr.sin_addr = *((struct in_addr *)CMSG_DATA(cmptr));
98 else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF)
99 if_index = ((struct sockaddr_dl *)CMSG_DATA(cmptr))->sdl_index;
100
101 if (if_index == 0 || !if_indextoname(if_index, ifr.ifr_name))
102 return;
103
Simon Kelley832af0b2007-01-21 20:01:28 +0000104#endif
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000105
106 if (addr.sin_addr.s_addr == 0)
107 return;
108
Simon Kelley5aabfc72007-08-29 11:24:47 +0100109 if (!iface_check(AF_INET, (struct all_addr *)&addr.sin_addr,
Simon Kelleyf2621c72007-04-29 19:47:21 +0100110 &ifr, &if_index))
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000111 return;
112
113 /* allowed interfaces are the same as for DHCP */
114 for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
115 if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0))
116 return;
117
118 }
119
Simon Kelley832af0b2007-01-21 20:01:28 +0000120 /* tell kernel to use ephemeral port */
121 addr.sin_port = 0;
122 addr.sin_family = AF_INET;
123#ifdef HAVE_SOCKADDR_SA_LEN
124 addr.sin_len = sizeof(addr);
125#endif
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000126
Simon Kelley5aabfc72007-08-29 11:24:47 +0100127 if (!(transfer = whine_malloc(sizeof(struct tftp_transfer))))
Simon Kelley832af0b2007-01-21 20:01:28 +0000128 return;
129
130 if ((transfer->sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
131 {
132 free(transfer);
133 return;
134 }
135
136 transfer->peer = peer;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100137 transfer->timeout = now + 2;
Simon Kelley832af0b2007-01-21 20:01:28 +0000138 transfer->backoff = 1;
139 transfer->block = 1;
140 transfer->blocksize = 512;
141 transfer->file = NULL;
142 transfer->opt_blocksize = transfer->opt_transize = 0;
143
144 if (bind(transfer->sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1 ||
145 !fix_fd(transfer->sockfd))
146 {
147 free_transfer(transfer);
148 return;
149 }
150
151 p = packet + 2;
152 end = packet + len;
153
154 if (ntohs(*((unsigned short *)packet)) != OP_RRQ ||
155 !(filename = next(&p, end)) ||
156 !(mode = next(&p, end)) ||
157 strcasecmp(mode, "octet") != 0)
158 len = tftp_err(ERR_ILL, packet, _("unsupported request from %s"), inet_ntoa(peer.sin_addr));
159 else
160 {
161 while ((opt = next(&p, end)))
162 {
163 if (strcasecmp(opt, "blksize") == 0 &&
Simon Kelley6b010842007-02-12 20:32:07 +0000164 (opt = next(&p, end)) &&
165 !(daemon->options & OPT_TFTP_NOBLOCK))
Simon Kelley832af0b2007-01-21 20:01:28 +0000166 {
167 transfer->blocksize = atoi(opt);
168 if (transfer->blocksize < 1)
169 transfer->blocksize = 1;
170 if (transfer->blocksize > (unsigned)daemon->packet_buff_sz - 4)
171 transfer->blocksize = (unsigned)daemon->packet_buff_sz - 4;
172 transfer->opt_blocksize = 1;
173 transfer->block = 0;
174 }
175
176 if (strcasecmp(opt, "tsize") == 0 && next(&p, end))
177 {
178 transfer->opt_transize = 1;
179 transfer->block = 0;
180 }
181 }
182
Simon Kelleyf2621c72007-04-29 19:47:21 +0100183 strcpy(daemon->namebuff, "/");
Simon Kelley832af0b2007-01-21 20:01:28 +0000184 if (daemon->tftp_prefix)
185 {
Simon Kelleyf2621c72007-04-29 19:47:21 +0100186 if (daemon->tftp_prefix[0] == '/')
187 daemon->namebuff[0] = 0;
188 strncat(daemon->namebuff, daemon->tftp_prefix, MAXDNAME);
189 if (daemon->tftp_prefix[strlen(daemon->tftp_prefix)-1] != '/')
Simon Kelley832af0b2007-01-21 20:01:28 +0000190 strncat(daemon->namebuff, "/", MAXDNAME);
Simon Kelley5aabfc72007-08-29 11:24:47 +0100191
192 if (daemon->options & OPT_TFTP_APREF)
193 {
194 size_t oldlen = strlen(daemon->namebuff);
195 struct stat statbuf;
196
197 strncat(daemon->namebuff, inet_ntoa(peer.sin_addr), MAXDNAME);
198 strncat(daemon->namebuff, "/", MAXDNAME);
199
200 /* remove unique-directory if it doesn't exist */
201 if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode))
202 daemon->namebuff[oldlen] = 0;
203 }
204
Simon Kelleyf2621c72007-04-29 19:47:21 +0100205 /* Absolute pathnames OK if they match prefix */
206 if (filename[0] == '/')
207 {
208 if (strstr(filename, daemon->namebuff) == filename)
209 daemon->namebuff[0] = 0;
210 else
211 filename++;
212 }
Simon Kelley832af0b2007-01-21 20:01:28 +0000213 }
Simon Kelleyf2621c72007-04-29 19:47:21 +0100214 else if (filename[0] == '/')
Simon Kelley832af0b2007-01-21 20:01:28 +0000215 daemon->namebuff[0] = 0;
Simon Kelley832af0b2007-01-21 20:01:28 +0000216 strncat(daemon->namebuff, filename, MAXDNAME);
217 daemon->namebuff[MAXDNAME-1] = 0;
218
Simon Kelley5aabfc72007-08-29 11:24:47 +0100219 /* check permissions and open file */
220 if ((transfer->file = check_tftp_fileperm(&len)))
Simon Kelley832af0b2007-01-21 20:01:28 +0000221 {
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000222 if ((len = get_block(packet, transfer)) == -1)
223 len = tftp_err_oops(packet, daemon->namebuff);
224 else
225 is_err = 0;
Simon Kelley832af0b2007-01-21 20:01:28 +0000226 }
227 }
228
229 while (sendto(transfer->sockfd, packet, len, 0,
230 (struct sockaddr *)&peer, sizeof(peer)) == -1 && errno == EINTR);
231
232 if (is_err)
233 free_transfer(transfer);
234 else
235 {
Simon Kelleyf2621c72007-04-29 19:47:21 +0100236 my_syslog(LOG_INFO, _("TFTP sent %s to %s"), daemon->namebuff, inet_ntoa(peer.sin_addr));
Simon Kelley832af0b2007-01-21 20:01:28 +0000237 transfer->next = daemon->tftp_trans;
238 daemon->tftp_trans = transfer;
239 }
240}
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000241
Simon Kelley5aabfc72007-08-29 11:24:47 +0100242static struct tftp_file *check_tftp_fileperm(ssize_t *len)
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000243{
244 char *packet = daemon->packet, *namebuff = daemon->namebuff;
245 struct tftp_file *file;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100246 struct tftp_transfer *t;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000247 uid_t uid = geteuid();
248 struct stat statbuf;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100249 int fd = -1;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000250
251 /* trick to ban moving out of the subtree */
252 if (daemon->tftp_prefix && strstr(namebuff, "/../"))
Simon Kelley5aabfc72007-08-29 11:24:47 +0100253 goto perm;
254
255 if ((fd = open(namebuff, O_RDONLY)) == -1)
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000256 {
Simon Kelley5aabfc72007-08-29 11:24:47 +0100257 if (errno == ENOENT)
258 {
259 *len = tftp_err(ERR_FNF, packet, _("file %s not found"), namebuff);
260 return NULL;
261 }
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000262 else if (errno == EACCES)
263 goto perm;
264 else
265 goto oops;
266 }
Simon Kelley5aabfc72007-08-29 11:24:47 +0100267
268 /* stat the file descriptor to avoid stat->open races */
269 if (fstat(fd, &statbuf) == -1)
270 goto oops;
271
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000272 /* running as root, must be world-readable */
273 if (uid == 0)
274 {
275 if (!(statbuf.st_mode & S_IROTH))
Simon Kelley5aabfc72007-08-29 11:24:47 +0100276 goto perm;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000277 }
278 /* in secure mode, must be owned by user running dnsmasq */
279 else if ((daemon->options & OPT_TFTP_SECURE) && uid != statbuf.st_uid)
Simon Kelley5aabfc72007-08-29 11:24:47 +0100280 goto perm;
281
282 /* If we're doing many tranfers from the same file, only
283 open it once this saves lots of file descriptors
284 when mass-booting a big cluster, for instance.
285 Be conservative and only share when inode and name match
286 this keeps error messages sane. */
287 for (t = daemon->tftp_trans; t; t = t->next)
288 if (t->file->dev == statbuf.st_dev &&
289 t->file->inode == statbuf.st_ino &&
290 strcmp(t->file->filename, namebuff) == 0)
291 {
292 close(fd);
293 t->file->refcount++;
294 return t->file;
295 }
296
297 if (!(file = whine_malloc(sizeof(struct tftp_file) + strlen(namebuff) + 1)))
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000298 {
299 errno = ENOMEM;
300 goto oops;
301 }
302
Simon Kelley5aabfc72007-08-29 11:24:47 +0100303 file->fd = fd;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000304 file->size = statbuf.st_size;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100305 file->dev = statbuf.st_dev;
306 file->inode = statbuf.st_ino;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000307 file->refcount = 1;
308 strcpy(file->filename, namebuff);
309 return file;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100310
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000311 perm:
Simon Kelley5aabfc72007-08-29 11:24:47 +0100312 errno = EACCES;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000313 *len = tftp_err(ERR_PERM, packet, _("cannot access %s: %s"), namebuff);
Simon Kelley5aabfc72007-08-29 11:24:47 +0100314 if (fd != -1)
315 close(fd);
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000316 return NULL;
317
Simon Kelley5aabfc72007-08-29 11:24:47 +0100318 oops:
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000319 *len = tftp_err_oops(packet, namebuff);
Simon Kelley5aabfc72007-08-29 11:24:47 +0100320 if (fd != -1)
321 close(fd);
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000322 return NULL;
323}
324
Simon Kelley5aabfc72007-08-29 11:24:47 +0100325void check_tftp_listeners(fd_set *rset, time_t now)
Simon Kelley832af0b2007-01-21 20:01:28 +0000326{
327 struct tftp_transfer *transfer, *tmp, **up;
328 ssize_t len;
329
330 struct ack {
331 unsigned short op, block;
332 } *mess = (struct ack *)daemon->packet;
333
334 /* Check for activity on any existing transfers */
335 for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp)
336 {
337 tmp = transfer->next;
338
339 if (FD_ISSET(transfer->sockfd, rset))
340 {
341 /* we overwrote the buffer... */
342 daemon->srv_save = NULL;
343
344 if ((len = recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)) >= (ssize_t)sizeof(struct ack))
345 {
346 if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block)
347 {
348 /* Got ack, ensure we take the (re)transmit path */
349 transfer->timeout = now;
350 transfer->backoff = 0;
351 transfer->block++;
352 }
353 else if (ntohs(mess->op) == OP_ERR)
354 {
355 char *p = daemon->packet + sizeof(struct ack);
356 char *end = daemon->packet + len;
357 char *err = next(&p, end);
358 /* Sanitise error message */
359 if (!err)
360 err = "";
361 else
362 {
363 char *q, *r;
364 for (q = r = err; *r; r++)
365 if (isprint(*r))
366 *(q++) = *r;
367 *q = 0;
368 }
Simon Kelleyf2621c72007-04-29 19:47:21 +0100369 my_syslog(LOG_ERR, _("TFTP error %d %s received from %s"),
370 (int)ntohs(mess->block), err,
371 inet_ntoa(transfer->peer.sin_addr));
Simon Kelley832af0b2007-01-21 20:01:28 +0000372
373 /* Got err, ensure we take abort */
374 transfer->timeout = now;
375 transfer->backoff = 100;
376 }
377 }
378 }
379
380 if (difftime(now, transfer->timeout) >= 0.0)
381 {
382 int endcon = 0;
383
384 /* timeout, retransmit */
Simon Kelley5aabfc72007-08-29 11:24:47 +0100385 transfer->timeout += 1 + (1<<transfer->backoff);
Simon Kelley832af0b2007-01-21 20:01:28 +0000386
387 /* we overwrote the buffer... */
388 daemon->srv_save = NULL;
389
390 if ((len = get_block(daemon->packet, transfer)) == -1)
391 {
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000392 len = tftp_err_oops(daemon->packet, transfer->file->filename);
Simon Kelley832af0b2007-01-21 20:01:28 +0000393 endcon = 1;
394 }
395 else if (++transfer->backoff > 5)
396 {
397 /* don't complain about timeout when we're awaiting the last
398 ACK, some clients never send it */
399 if (len != 0)
Simon Kelleyf2621c72007-04-29 19:47:21 +0100400 my_syslog(LOG_ERR, _("TFTP failed sending %s to %s"),
401 transfer->file->filename, inet_ntoa(transfer->peer.sin_addr));
Simon Kelley832af0b2007-01-21 20:01:28 +0000402 len = 0;
403 }
404
405 if (len != 0)
406 while(sendto(transfer->sockfd, daemon->packet, len, 0,
407 (struct sockaddr *)&transfer->peer, sizeof(transfer->peer)) == -1 && errno == EINTR);
408
409 if (endcon || len == 0)
410 {
411 /* unlink */
412 *up = tmp;
413 free_transfer(transfer);
414 continue;
415 }
416 }
417
418 up = &transfer->next;
419 }
420}
421
422static void free_transfer(struct tftp_transfer *transfer)
423{
424 close(transfer->sockfd);
425 if (transfer->file && (--transfer->file->refcount) == 0)
426 {
427 close(transfer->file->fd);
428 free(transfer->file);
429 }
430 free(transfer);
431}
432
433static char *next(char **p, char *end)
434{
435 char *ret = *p;
436 size_t len;
437
438 if (*(end-1) != 0 ||
439 *p == end ||
440 (len = strlen(ret)) == 0)
441 return NULL;
442
443 *p += len + 1;
444 return ret;
445}
446
447static ssize_t tftp_err(int err, char *packet, char *message, char *file)
448{
449 struct errmess {
450 unsigned short op, err;
451 char message[];
452 } *mess = (struct errmess *)packet;
453 ssize_t ret = 4;
454 char *errstr = strerror(errno);
455
456 mess->op = htons(OP_ERR);
457 mess->err = htons(err);
458 ret += (snprintf(mess->message, 500, message, file, errstr) + 1);
459 if (err != ERR_FNF)
Simon Kelleyf2621c72007-04-29 19:47:21 +0100460 my_syslog(LOG_ERR, "TFTP %s", mess->message);
Simon Kelley832af0b2007-01-21 20:01:28 +0000461
462 return ret;
463}
464
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000465static ssize_t tftp_err_oops(char *packet, char *file)
466{
467 return tftp_err(ERR_NOTDEF, packet, _("cannot read %s: %s"), file);
468}
469
Simon Kelley832af0b2007-01-21 20:01:28 +0000470/* return -1 for error, zero for done. */
471static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
472{
473 if (transfer->block == 0)
474 {
475 /* send OACK */
476 char *p;
477 struct oackmess {
478 unsigned short op;
479 char data[];
480 } *mess = (struct oackmess *)packet;
481
482 p = mess->data;
483 mess->op = htons(OP_OACK);
484 if (transfer->opt_blocksize)
485 {
486 p += (sprintf(p, "blksize") + 1);
487 p += (sprintf(p, "%d", transfer->blocksize) + 1);
488 }
489 if (transfer->opt_transize)
490 {
491 p += (sprintf(p,"tsize") + 1);
492 p += (sprintf(p, "%u", (unsigned int)transfer->file->size) + 1);
493 }
494
495 return p - packet;
496 }
497 else
498 {
499 /* send data packet */
500 struct datamess {
501 unsigned short op, block;
502 unsigned char data[];
503 } *mess = (struct datamess *)packet;
504
505 off_t offset = transfer->blocksize * (transfer->block - 1);
506 size_t size = transfer->file->size - offset;
507
508 if (offset > transfer->file->size)
509 return 0; /* finished */
510
511 if (size > transfer->blocksize)
512 size = transfer->blocksize;
513
514 lseek(transfer->file->fd, offset, SEEK_SET);
515
516 mess->op = htons(OP_DATA);
517 mess->block = htons((unsigned short)(transfer->block));
518
519 if (!read_write(transfer->file->fd, mess->data, size, 1))
520 return -1;
521 else
522 return size + 4;
523 }
524}
525
526#endif