blob: dc4aa85964ec6cbe516f2333b87723b9ddad66d9 [file] [log] [blame]
Simon Kelleyc49778d2016-01-06 18:52:33 +00001/* dnsmasq is Copyright (c) 2000-2016 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
Simon Kelley824af852008-02-12 20:43:05 +00005 the Free Software Foundation; version 2 dated June, 1991, or
6 (at your option) version 3 dated 29 June, 2007.
7
Simon Kelley832af0b2007-01-21 20:01:28 +00008 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
Simon Kelley824af852008-02-12 20:43:05 +000012
Simon Kelley73a08a22009-02-05 20:28:08 +000013 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>.
Simon Kelley832af0b2007-01-21 20:01:28 +000015*/
16
17#include "dnsmasq.h"
18
19#ifdef HAVE_TFTP
20
Simon Kelley8bc4cec2012-07-03 21:04:11 +010021static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix);
Simon Kelley832af0b2007-01-21 20:01:28 +000022static void free_transfer(struct tftp_transfer *transfer);
23static ssize_t tftp_err(int err, char *packet, char *mess, char *file);
Simon Kelley1b7ecd12007-02-05 14:57:57 +000024static ssize_t tftp_err_oops(char *packet, char *file);
Simon Kelley832af0b2007-01-21 20:01:28 +000025static ssize_t get_block(char *packet, struct tftp_transfer *transfer);
26static char *next(char **p, char *end);
Simon Kelley42fb8152012-04-20 17:15:01 +010027static void sanitise(char *buf);
Simon Kelley832af0b2007-01-21 20:01:28 +000028
29#define OP_RRQ 1
30#define OP_WRQ 2
31#define OP_DATA 3
32#define OP_ACK 4
33#define OP_ERR 5
34#define OP_OACK 6
35
36#define ERR_NOTDEF 0
37#define ERR_FNF 1
38#define ERR_PERM 2
39#define ERR_FULL 3
40#define ERR_ILL 4
41
Simon Kelley5aabfc72007-08-29 11:24:47 +010042void tftp_request(struct listener *listen, time_t now)
Simon Kelley832af0b2007-01-21 20:01:28 +000043{
44 ssize_t len;
45 char *packet = daemon->packet;
46 char *filename, *mode, *p, *end, *opt;
Simon Kelley28866e92011-02-14 20:19:14 +000047 union mysockaddr addr, peer;
Simon Kelley832af0b2007-01-21 20:01:28 +000048 struct msghdr msg;
Simon Kelley832af0b2007-01-21 20:01:28 +000049 struct iovec iov;
Simon Kelley1f15b812009-10-13 17:49:32 +010050 struct ifreq ifr;
Simon Kelley8bc4cec2012-07-03 21:04:11 +010051 int is_err = 1, if_index = 0, mtu = 0;
Simon Kelley832af0b2007-01-21 20:01:28 +000052 struct iname *tmp;
Simon Kelley5aabfc72007-08-29 11:24:47 +010053 struct tftp_transfer *transfer;
Simon Kelley824af852008-02-12 20:43:05 +000054 int port = daemon->start_tftp_port; /* may be zero to use ephemeral port */
55#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
Simon Kelley1f15b812009-10-13 17:49:32 +010056 int mtuflag = IP_PMTUDISC_DONT;
Simon Kelley824af852008-02-12 20:43:05 +000057#endif
Simon Kelley8ef5ada2010-06-03 19:42:45 +010058 char namebuff[IF_NAMESIZE];
Simon Kelley52d4abf2012-03-21 21:39:48 +000059 char *name = NULL;
Simon Kelley8ef5ada2010-06-03 19:42:45 +010060 char *prefix = daemon->tftp_prefix;
61 struct tftp_prefix *pref;
Simon Kelleye25db1f2013-01-29 22:10:26 +000062 struct all_addr addra;
Simon Kelley2329bef2013-12-03 13:41:16 +000063#ifdef HAVE_IPV6
64 /* Can always get recvd interface for IPv6 */
65 int check_dest = !option_bool(OPT_NOWILD) || listen->family == AF_INET6;
66#else
67 int check_dest = !option_bool(OPT_NOWILD);
68#endif
Simon Kelley832af0b2007-01-21 20:01:28 +000069 union {
70 struct cmsghdr align; /* this ensures alignment */
Simon Kelley28866e92011-02-14 20:19:14 +000071#ifdef HAVE_IPV6
72 char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
73#endif
Simon Kelley824af852008-02-12 20:43:05 +000074#if defined(HAVE_LINUX_NETWORK)
Simon Kelley832af0b2007-01-21 20:01:28 +000075 char control[CMSG_SPACE(sizeof(struct in_pktinfo))];
Simon Kelley824af852008-02-12 20:43:05 +000076#elif defined(HAVE_SOLARIS_NETWORK)
77 char control[CMSG_SPACE(sizeof(unsigned int))];
Simon Kelley7622fc02009-06-04 20:32:05 +010078#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
Simon Kelley832af0b2007-01-21 20:01:28 +000079 char control[CMSG_SPACE(sizeof(struct sockaddr_dl))];
80#endif
81 } control_u;
82
83 msg.msg_controllen = sizeof(control_u);
84 msg.msg_control = control_u.control;
85 msg.msg_flags = 0;
86 msg.msg_name = &peer;
87 msg.msg_namelen = sizeof(peer);
88 msg.msg_iov = &iov;
89 msg.msg_iovlen = 1;
90
91 iov.iov_base = packet;
92 iov.iov_len = daemon->packet_buff_sz;
93
94 /* we overwrote the buffer... */
95 daemon->srv_save = NULL;
96
97 if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2)
98 return;
Simon Kelley2329bef2013-12-03 13:41:16 +000099
100 /* Can always get recvd interface for IPv6 */
101 if (!check_dest)
Simon Kelley1f15b812009-10-13 17:49:32 +0100102 {
Simon Kelley52d4abf2012-03-21 21:39:48 +0000103 if (listen->iface)
104 {
105 addr = listen->iface->addr;
Simon Kelley52d4abf2012-03-21 21:39:48 +0000106 name = listen->iface->name;
Simon Kelleybec366b2016-02-24 22:03:26 +0000107 mtu = listen->iface->mtu;
108 if (daemon->tftp_mtu != 0 && daemon->tftp_mtu < mtu)
109 mtu = daemon->tftp_mtu;
Simon Kelley52d4abf2012-03-21 21:39:48 +0000110 }
111 else
112 {
113 /* we're listening on an address that doesn't appear on an interface,
114 ask the kernel what the socket is bound to */
115 socklen_t tcp_len = sizeof(union mysockaddr);
116 if (getsockname(listen->tftpfd, (struct sockaddr *)&addr, &tcp_len) == -1)
117 return;
118 }
Simon Kelley1f15b812009-10-13 17:49:32 +0100119 }
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000120 else
121 {
Simon Kelley7622fc02009-06-04 20:32:05 +0100122 struct cmsghdr *cmptr;
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100123
Simon Kelley28866e92011-02-14 20:19:14 +0000124 if (msg.msg_controllen < sizeof(struct cmsghdr))
125 return;
126
127 addr.sa.sa_family = listen->family;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000128
Simon Kelley832af0b2007-01-21 20:01:28 +0000129#if defined(HAVE_LINUX_NETWORK)
Simon Kelley28866e92011-02-14 20:19:14 +0000130 if (listen->family == AF_INET)
131 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
Simon Kelleyc72daea2012-01-05 21:33:27 +0000132 if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO)
Simon Kelley28866e92011-02-14 20:19:14 +0000133 {
134 union {
135 unsigned char *c;
136 struct in_pktinfo *p;
137 } p;
138 p.c = CMSG_DATA(cmptr);
139 addr.in.sin_addr = p.p->ipi_spec_dst;
140 if_index = p.p->ipi_ifindex;
141 }
142
143#elif defined(HAVE_SOLARIS_NETWORK)
144 if (listen->family == AF_INET)
145 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000146 {
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100147 union {
148 unsigned char *c;
Simon Kelley28866e92011-02-14 20:19:14 +0000149 struct in_addr *a;
150 unsigned int *i;
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100151 } p;
152 p.c = CMSG_DATA(cmptr);
Simon Kelley28866e92011-02-14 20:19:14 +0000153 if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR)
154 addr.in.sin_addr = *(p.a);
155 else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF)
156 if_index = *(p.i);
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000157 }
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000158
Simon Kelley832af0b2007-01-21 20:01:28 +0000159#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
Simon Kelley28866e92011-02-14 20:19:14 +0000160 if (listen->family == AF_INET)
161 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
162 {
163 union {
164 unsigned char *c;
165 struct in_addr *a;
166 struct sockaddr_dl *s;
167 } p;
168 p.c = CMSG_DATA(cmptr);
169 if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR)
170 addr.in.sin_addr = *(p.a);
171 else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF)
172 if_index = p.s->sdl_index;
173 }
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100174
Simon Kelley824af852008-02-12 20:43:05 +0000175#endif
Simon Kelley28866e92011-02-14 20:19:14 +0000176
177#ifdef HAVE_IPV6
178 if (listen->family == AF_INET6)
179 {
180 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
Simon Kelleyc72daea2012-01-05 21:33:27 +0000181 if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
Simon Kelley28866e92011-02-14 20:19:14 +0000182 {
183 union {
184 unsigned char *c;
185 struct in6_pktinfo *p;
186 } p;
187 p.c = CMSG_DATA(cmptr);
188
189 addr.in6.sin6_addr = p.p->ipi6_addr;
190 if_index = p.p->ipi6_ifindex;
191 }
192 }
193#endif
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000194
Simon Kelley28866e92011-02-14 20:19:14 +0000195 if (!indextoname(listen->tftpfd, if_index, namebuff))
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000196 return;
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100197
198 name = namebuff;
Simon Kelleye25db1f2013-01-29 22:10:26 +0000199
200 addra.addr.addr4 = addr.in.sin_addr;
Simon Kelley28866e92011-02-14 20:19:14 +0000201
202#ifdef HAVE_IPV6
203 if (listen->family == AF_INET6)
Simon Kelleye25db1f2013-01-29 22:10:26 +0000204 addra.addr.addr6 = addr.in6.sin6_addr;
205#endif
206
Simon Kelley2937f8a2013-07-29 19:49:07 +0100207 if (daemon->tftp_interfaces)
Simon Kelley3169daa2012-08-13 17:39:57 +0100208 {
Simon Kelley2937f8a2013-07-29 19:49:07 +0100209 /* dedicated tftp interface list */
210 for (tmp = daemon->tftp_interfaces; tmp; tmp = tmp->next)
211 if (tmp->name && wildcard_match(tmp->name, name))
212 break;
213
214 if (!tmp)
Simon Kelley3169daa2012-08-13 17:39:57 +0100215 return;
216 }
Simon Kelley2937f8a2013-07-29 19:49:07 +0100217 else
218 {
219 /* Do the same as DHCP */
220 if (!iface_check(listen->family, &addra, name, NULL))
221 {
222 if (!option_bool(OPT_CLEVERBIND))
223 enumerate_interfaces(0);
224 if (!loopback_exception(listen->tftpfd, listen->family, &addra, name) &&
225 !label_exception(if_index, listen->family, &addra) )
226 return;
227 }
228
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100229#ifdef HAVE_DHCP
Simon Kelley2937f8a2013-07-29 19:49:07 +0100230 /* allowed interfaces are the same as for DHCP */
231 for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
232 if (tmp->name && wildcard_match(tmp->name, name))
233 return;
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100234#endif
Simon Kelley2937f8a2013-07-29 19:49:07 +0100235 }
236
Simon Kelley316e2732010-01-22 20:16:09 +0000237 strncpy(ifr.ifr_name, name, IF_NAMESIZE);
Simon Kelley1f15b812009-10-13 17:49:32 +0100238 if (ioctl(listen->tftpfd, SIOCGIFMTU, &ifr) != -1)
Simon Kelleybec366b2016-02-24 22:03:26 +0000239 {
240 mtu = ifr.ifr_mtu;
241 if (daemon->tftp_mtu != 0 && daemon->tftp_mtu < mtu)
242 mtu = daemon->tftp_mtu;
243 }
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000244 }
Stefan Tomanek7aa970e2015-04-01 17:55:07 +0100245
Simon Kelleybec366b2016-02-24 22:03:26 +0000246 /* Failed to get interface mtu - can use configured value. */
247 if (mtu == 0)
248 mtu = daemon->tftp_mtu;
249
Simon Kelley52d4abf2012-03-21 21:39:48 +0000250 if (name)
251 {
252 /* check for per-interface prefix */
253 for (pref = daemon->if_prefix; pref; pref = pref->next)
254 if (strcmp(pref->interface, name) == 0)
Simon Kelley8bc4cec2012-07-03 21:04:11 +0100255 prefix = pref->prefix;
Simon Kelley52d4abf2012-03-21 21:39:48 +0000256 }
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100257
Simon Kelley28866e92011-02-14 20:19:14 +0000258 if (listen->family == AF_INET)
Simon Kelley7de060b2011-08-26 17:24:52 +0100259 {
260 addr.in.sin_port = htons(port);
261#ifdef HAVE_SOCKADDR_SA_LEN
262 addr.in.sin_len = sizeof(addr.in);
263#endif
264 }
Simon Kelley28866e92011-02-14 20:19:14 +0000265#ifdef HAVE_IPV6
266 else
267 {
268 addr.in6.sin6_port = htons(port);
269 addr.in6.sin6_flowinfo = 0;
Simon Kelley7de060b2011-08-26 17:24:52 +0100270 addr.in6.sin6_scope_id = 0;
271#ifdef HAVE_SOCKADDR_SA_LEN
272 addr.in6.sin6_len = sizeof(addr.in6);
273#endif
Simon Kelley28866e92011-02-14 20:19:14 +0000274 }
275#endif
276
Simon Kelley5aabfc72007-08-29 11:24:47 +0100277 if (!(transfer = whine_malloc(sizeof(struct tftp_transfer))))
Simon Kelley832af0b2007-01-21 20:01:28 +0000278 return;
279
Simon Kelley28866e92011-02-14 20:19:14 +0000280 if ((transfer->sockfd = socket(listen->family, SOCK_DGRAM, 0)) == -1)
Simon Kelley832af0b2007-01-21 20:01:28 +0000281 {
282 free(transfer);
283 return;
284 }
285
286 transfer->peer = peer;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100287 transfer->timeout = now + 2;
Simon Kelley832af0b2007-01-21 20:01:28 +0000288 transfer->backoff = 1;
289 transfer->block = 1;
290 transfer->blocksize = 512;
Simon Kelley9e038942008-05-30 20:06:34 +0100291 transfer->offset = 0;
Simon Kelley832af0b2007-01-21 20:01:28 +0000292 transfer->file = NULL;
293 transfer->opt_blocksize = transfer->opt_transize = 0;
Simon Kelley9e038942008-05-30 20:06:34 +0100294 transfer->netascii = transfer->carrylf = 0;
Simon Kelley28866e92011-02-14 20:19:14 +0000295
Simon Kelleyc72daea2012-01-05 21:33:27 +0000296 prettyprint_addr(&peer, daemon->addrbuff);
Simon Kelley28866e92011-02-14 20:19:14 +0000297
Simon Kelley824af852008-02-12 20:43:05 +0000298 /* if we have a nailed-down range, iterate until we find a free one. */
299 while (1)
Simon Kelley832af0b2007-01-21 20:01:28 +0000300 {
Simon Kelley7de060b2011-08-26 17:24:52 +0100301 if (bind(transfer->sockfd, &addr.sa, sa_len(&addr)) == -1 ||
Simon Kelley824af852008-02-12 20:43:05 +0000302#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
Simon Kelleyc72daea2012-01-05 21:33:27 +0000303 setsockopt(transfer->sockfd, IPPROTO_IP, IP_MTU_DISCOVER, &mtuflag, sizeof(mtuflag)) == -1 ||
Simon Kelley824af852008-02-12 20:43:05 +0000304#endif
305 !fix_fd(transfer->sockfd))
306 {
307 if (errno == EADDRINUSE && daemon->start_tftp_port != 0)
308 {
309 if (++port <= daemon->end_tftp_port)
310 {
Simon Kelley28866e92011-02-14 20:19:14 +0000311 if (listen->family == AF_INET)
312 addr.in.sin_port = htons(port);
313#ifdef HAVE_IPV6
314 else
315 addr.in6.sin6_port = htons(port);
316#endif
Simon Kelley824af852008-02-12 20:43:05 +0000317 continue;
318 }
Simon Kelley7622fc02009-06-04 20:32:05 +0100319 my_syslog(MS_TFTP | LOG_ERR, _("unable to get free port for TFTP"));
Simon Kelley824af852008-02-12 20:43:05 +0000320 }
321 free_transfer(transfer);
322 return;
323 }
324 break;
Simon Kelley832af0b2007-01-21 20:01:28 +0000325 }
Simon Kelley824af852008-02-12 20:43:05 +0000326
Simon Kelley832af0b2007-01-21 20:01:28 +0000327 p = packet + 2;
328 end = packet + len;
329
330 if (ntohs(*((unsigned short *)packet)) != OP_RRQ ||
331 !(filename = next(&p, end)) ||
332 !(mode = next(&p, end)) ||
Simon Kelley9e038942008-05-30 20:06:34 +0100333 (strcasecmp(mode, "octet") != 0 && strcasecmp(mode, "netascii") != 0))
Simon Kelley6a69ab52012-04-24 14:42:26 +0100334 {
335 len = tftp_err(ERR_ILL, packet, _("unsupported request from %s"), daemon->addrbuff);
336 is_err = 1;
337 }
Simon Kelley832af0b2007-01-21 20:01:28 +0000338 else
339 {
Simon Kelley9e038942008-05-30 20:06:34 +0100340 if (strcasecmp(mode, "netascii") == 0)
341 transfer->netascii = 1;
342
Simon Kelley832af0b2007-01-21 20:01:28 +0000343 while ((opt = next(&p, end)))
344 {
Simon Kelley77e94da2009-08-31 17:32:17 +0100345 if (strcasecmp(opt, "blksize") == 0)
Simon Kelley832af0b2007-01-21 20:01:28 +0000346 {
Simon Kelley8bc4cec2012-07-03 21:04:11 +0100347 if ((opt = next(&p, end)) && !option_bool(OPT_TFTP_NOBLOCK))
Simon Kelley77e94da2009-08-31 17:32:17 +0100348 {
349 transfer->blocksize = atoi(opt);
350 if (transfer->blocksize < 1)
351 transfer->blocksize = 1;
352 if (transfer->blocksize > (unsigned)daemon->packet_buff_sz - 4)
353 transfer->blocksize = (unsigned)daemon->packet_buff_sz - 4;
Simon Kelley1f15b812009-10-13 17:49:32 +0100354 /* 32 bytes for IP, UDP and TFTP headers */
355 if (mtu != 0 && transfer->blocksize > (unsigned)mtu - 32)
356 transfer->blocksize = (unsigned)mtu - 32;
Simon Kelley77e94da2009-08-31 17:32:17 +0100357 transfer->opt_blocksize = 1;
358 transfer->block = 0;
359 }
Simon Kelley832af0b2007-01-21 20:01:28 +0000360 }
Simon Kelley77e94da2009-08-31 17:32:17 +0100361 else if (strcasecmp(opt, "tsize") == 0 && next(&p, end) && !transfer->netascii)
Simon Kelley832af0b2007-01-21 20:01:28 +0000362 {
363 transfer->opt_transize = 1;
364 transfer->block = 0;
365 }
366 }
367
Simon Kelley1f15b812009-10-13 17:49:32 +0100368 /* cope with backslashes from windows boxen. */
Simon Kelley61ce6002012-04-20 21:28:49 +0100369 for (p = filename; *p; p++)
370 if (*p == '\\')
371 *p = '/';
372 else if (option_bool(OPT_TFTP_LC))
373 *p = tolower(*p);
374
Simon Kelleyf2621c72007-04-29 19:47:21 +0100375 strcpy(daemon->namebuff, "/");
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100376 if (prefix)
Simon Kelley832af0b2007-01-21 20:01:28 +0000377 {
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100378 if (prefix[0] == '/')
Simon Kelleyf2621c72007-04-29 19:47:21 +0100379 daemon->namebuff[0] = 0;
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100380 strncat(daemon->namebuff, prefix, (MAXDNAME-1) - strlen(daemon->namebuff));
381 if (prefix[strlen(prefix)-1] != '/')
Simon Kelley77e94da2009-08-31 17:32:17 +0100382 strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff));
Simon Kelley5aabfc72007-08-29 11:24:47 +0100383
Simon Kelley8bc4cec2012-07-03 21:04:11 +0100384 if (option_bool(OPT_TFTP_APREF))
Simon Kelley5aabfc72007-08-29 11:24:47 +0100385 {
386 size_t oldlen = strlen(daemon->namebuff);
387 struct stat statbuf;
388
Simon Kelleyc72daea2012-01-05 21:33:27 +0000389 strncat(daemon->namebuff, daemon->addrbuff, (MAXDNAME-1) - strlen(daemon->namebuff));
Simon Kelley77e94da2009-08-31 17:32:17 +0100390 strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff));
Simon Kelley5aabfc72007-08-29 11:24:47 +0100391
392 /* remove unique-directory if it doesn't exist */
393 if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode))
394 daemon->namebuff[oldlen] = 0;
395 }
396
Simon Kelleyf2621c72007-04-29 19:47:21 +0100397 /* Absolute pathnames OK if they match prefix */
398 if (filename[0] == '/')
399 {
400 if (strstr(filename, daemon->namebuff) == filename)
401 daemon->namebuff[0] = 0;
402 else
403 filename++;
404 }
Simon Kelley832af0b2007-01-21 20:01:28 +0000405 }
Simon Kelleyf2621c72007-04-29 19:47:21 +0100406 else if (filename[0] == '/')
Simon Kelley832af0b2007-01-21 20:01:28 +0000407 daemon->namebuff[0] = 0;
Simon Kelley77e94da2009-08-31 17:32:17 +0100408 strncat(daemon->namebuff, filename, (MAXDNAME-1) - strlen(daemon->namebuff));
Simon Kelley832af0b2007-01-21 20:01:28 +0000409
Simon Kelley5aabfc72007-08-29 11:24:47 +0100410 /* check permissions and open file */
Simon Kelley8bc4cec2012-07-03 21:04:11 +0100411 if ((transfer->file = check_tftp_fileperm(&len, prefix)))
Simon Kelley832af0b2007-01-21 20:01:28 +0000412 {
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000413 if ((len = get_block(packet, transfer)) == -1)
414 len = tftp_err_oops(packet, daemon->namebuff);
415 else
416 is_err = 0;
Simon Kelley832af0b2007-01-21 20:01:28 +0000417 }
418 }
419
420 while (sendto(transfer->sockfd, packet, len, 0,
Simon Kelley04363602012-04-27 10:11:51 +0100421 (struct sockaddr *)&peer, sa_len(&peer)) == -1 && errno == EINTR);
Simon Kelley832af0b2007-01-21 20:01:28 +0000422
423 if (is_err)
424 free_transfer(transfer);
425 else
426 {
Simon Kelley832af0b2007-01-21 20:01:28 +0000427 transfer->next = daemon->tftp_trans;
428 daemon->tftp_trans = transfer;
429 }
430}
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000431
Simon Kelley8bc4cec2012-07-03 21:04:11 +0100432static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix)
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000433{
434 char *packet = daemon->packet, *namebuff = daemon->namebuff;
435 struct tftp_file *file;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100436 struct tftp_transfer *t;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000437 uid_t uid = geteuid();
438 struct stat statbuf;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100439 int fd = -1;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000440
441 /* trick to ban moving out of the subtree */
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100442 if (prefix && strstr(namebuff, "/../"))
Simon Kelley5aabfc72007-08-29 11:24:47 +0100443 goto perm;
444
445 if ((fd = open(namebuff, O_RDONLY)) == -1)
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000446 {
Simon Kelley5aabfc72007-08-29 11:24:47 +0100447 if (errno == ENOENT)
448 {
449 *len = tftp_err(ERR_FNF, packet, _("file %s not found"), namebuff);
450 return NULL;
451 }
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000452 else if (errno == EACCES)
453 goto perm;
454 else
455 goto oops;
456 }
Simon Kelley5aabfc72007-08-29 11:24:47 +0100457
458 /* stat the file descriptor to avoid stat->open races */
459 if (fstat(fd, &statbuf) == -1)
460 goto oops;
461
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000462 /* running as root, must be world-readable */
463 if (uid == 0)
464 {
465 if (!(statbuf.st_mode & S_IROTH))
Simon Kelley5aabfc72007-08-29 11:24:47 +0100466 goto perm;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000467 }
468 /* in secure mode, must be owned by user running dnsmasq */
Simon Kelley8bc4cec2012-07-03 21:04:11 +0100469 else if (option_bool(OPT_TFTP_SECURE) && uid != statbuf.st_uid)
Simon Kelley5aabfc72007-08-29 11:24:47 +0100470 goto perm;
471
472 /* If we're doing many tranfers from the same file, only
473 open it once this saves lots of file descriptors
474 when mass-booting a big cluster, for instance.
475 Be conservative and only share when inode and name match
476 this keeps error messages sane. */
477 for (t = daemon->tftp_trans; t; t = t->next)
478 if (t->file->dev == statbuf.st_dev &&
479 t->file->inode == statbuf.st_ino &&
480 strcmp(t->file->filename, namebuff) == 0)
481 {
482 close(fd);
483 t->file->refcount++;
484 return t->file;
485 }
486
487 if (!(file = whine_malloc(sizeof(struct tftp_file) + strlen(namebuff) + 1)))
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000488 {
489 errno = ENOMEM;
490 goto oops;
491 }
492
Simon Kelley5aabfc72007-08-29 11:24:47 +0100493 file->fd = fd;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000494 file->size = statbuf.st_size;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100495 file->dev = statbuf.st_dev;
496 file->inode = statbuf.st_ino;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000497 file->refcount = 1;
498 strcpy(file->filename, namebuff);
499 return file;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100500
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000501 perm:
Simon Kelley5aabfc72007-08-29 11:24:47 +0100502 errno = EACCES;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000503 *len = tftp_err(ERR_PERM, packet, _("cannot access %s: %s"), namebuff);
Simon Kelley5aabfc72007-08-29 11:24:47 +0100504 if (fd != -1)
505 close(fd);
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000506 return NULL;
507
Simon Kelley5aabfc72007-08-29 11:24:47 +0100508 oops:
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000509 *len = tftp_err_oops(packet, namebuff);
Simon Kelley5aabfc72007-08-29 11:24:47 +0100510 if (fd != -1)
511 close(fd);
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000512 return NULL;
513}
514
Simon Kelleyb842bc92015-07-12 21:09:11 +0100515void check_tftp_listeners(time_t now)
Simon Kelley832af0b2007-01-21 20:01:28 +0000516{
517 struct tftp_transfer *transfer, *tmp, **up;
518 ssize_t len;
519
520 struct ack {
521 unsigned short op, block;
522 } *mess = (struct ack *)daemon->packet;
523
524 /* Check for activity on any existing transfers */
525 for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp)
526 {
527 tmp = transfer->next;
528
Simon Kelley6a69ab52012-04-24 14:42:26 +0100529 prettyprint_addr(&transfer->peer, daemon->addrbuff);
530
Simon Kelleyb842bc92015-07-12 21:09:11 +0100531 if (poll_check(transfer->sockfd, POLLIN))
Simon Kelley832af0b2007-01-21 20:01:28 +0000532 {
533 /* we overwrote the buffer... */
534 daemon->srv_save = NULL;
535
536 if ((len = recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)) >= (ssize_t)sizeof(struct ack))
537 {
538 if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block)
539 {
540 /* Got ack, ensure we take the (re)transmit path */
541 transfer->timeout = now;
542 transfer->backoff = 0;
Simon Kelley9e038942008-05-30 20:06:34 +0100543 if (transfer->block++ != 0)
544 transfer->offset += transfer->blocksize - transfer->expansion;
Simon Kelley832af0b2007-01-21 20:01:28 +0000545 }
546 else if (ntohs(mess->op) == OP_ERR)
547 {
548 char *p = daemon->packet + sizeof(struct ack);
549 char *end = daemon->packet + len;
550 char *err = next(&p, end);
Simon Kelley28866e92011-02-14 20:19:14 +0000551
Simon Kelley832af0b2007-01-21 20:01:28 +0000552 /* Sanitise error message */
553 if (!err)
554 err = "";
555 else
Simon Kelley7a14dfe2012-04-20 20:50:42 +0100556 sanitise(err);
Simon Kelley6a69ab52012-04-24 14:42:26 +0100557
Simon Kelley316e2732010-01-22 20:16:09 +0000558 my_syslog(MS_TFTP | LOG_ERR, _("error %d %s received from %s"),
Simon Kelleyf2621c72007-04-29 19:47:21 +0100559 (int)ntohs(mess->block), err,
Simon Kelleyc72daea2012-01-05 21:33:27 +0000560 daemon->addrbuff);
Simon Kelley832af0b2007-01-21 20:01:28 +0000561
562 /* Got err, ensure we take abort */
563 transfer->timeout = now;
564 transfer->backoff = 100;
565 }
566 }
567 }
568
569 if (difftime(now, transfer->timeout) >= 0.0)
570 {
571 int endcon = 0;
572
573 /* timeout, retransmit */
Simon Kelley5aabfc72007-08-29 11:24:47 +0100574 transfer->timeout += 1 + (1<<transfer->backoff);
Simon Kelley832af0b2007-01-21 20:01:28 +0000575
576 /* we overwrote the buffer... */
577 daemon->srv_save = NULL;
578
579 if ((len = get_block(daemon->packet, transfer)) == -1)
580 {
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000581 len = tftp_err_oops(daemon->packet, transfer->file->filename);
Simon Kelley832af0b2007-01-21 20:01:28 +0000582 endcon = 1;
583 }
Simon Kelley42fb8152012-04-20 17:15:01 +0100584 /* don't complain about timeout when we're awaiting the last
585 ACK, some clients never send it */
Simon Kelleyb4b93082013-06-19 10:31:23 +0100586 else if (++transfer->backoff > 7 && len != 0)
Simon Kelley832af0b2007-01-21 20:01:28 +0000587 {
Simon Kelley42fb8152012-04-20 17:15:01 +0100588 endcon = 1;
589 len = 0;
Simon Kelley832af0b2007-01-21 20:01:28 +0000590 }
Simon Kelley42fb8152012-04-20 17:15:01 +0100591
Simon Kelley832af0b2007-01-21 20:01:28 +0000592 if (len != 0)
593 while(sendto(transfer->sockfd, daemon->packet, len, 0,
Simon Kelley04363602012-04-27 10:11:51 +0100594 (struct sockaddr *)&transfer->peer, sa_len(&transfer->peer)) == -1 && errno == EINTR);
Simon Kelley832af0b2007-01-21 20:01:28 +0000595
596 if (endcon || len == 0)
597 {
Simon Kelley42fb8152012-04-20 17:15:01 +0100598 strcpy(daemon->namebuff, transfer->file->filename);
599 sanitise(daemon->namebuff);
600 my_syslog(MS_TFTP | LOG_INFO, endcon ? _("failed sending %s to %s") : _("sent %s to %s"), daemon->namebuff, daemon->addrbuff);
Simon Kelley832af0b2007-01-21 20:01:28 +0000601 /* unlink */
602 *up = tmp;
Simon Kelley231d0612012-04-27 13:50:45 +0100603 if (endcon)
604 free_transfer(transfer);
605 else
606 {
607 /* put on queue to be sent to script and deleted */
608 transfer->next = daemon->tftp_done_trans;
609 daemon->tftp_done_trans = transfer;
610 }
Simon Kelley832af0b2007-01-21 20:01:28 +0000611 continue;
612 }
613 }
614
615 up = &transfer->next;
Simon Kelley28866e92011-02-14 20:19:14 +0000616 }
Simon Kelley832af0b2007-01-21 20:01:28 +0000617}
618
619static void free_transfer(struct tftp_transfer *transfer)
620{
621 close(transfer->sockfd);
622 if (transfer->file && (--transfer->file->refcount) == 0)
623 {
624 close(transfer->file->fd);
625 free(transfer->file);
626 }
627 free(transfer);
628}
629
630static char *next(char **p, char *end)
631{
632 char *ret = *p;
633 size_t len;
634
635 if (*(end-1) != 0 ||
636 *p == end ||
637 (len = strlen(ret)) == 0)
638 return NULL;
639
640 *p += len + 1;
641 return ret;
642}
643
Simon Kelley42fb8152012-04-20 17:15:01 +0100644static void sanitise(char *buf)
645{
Simon Kelley7a14dfe2012-04-20 20:50:42 +0100646 unsigned char *q, *r;
647 for (q = r = (unsigned char *)buf; *r; r++)
Simon Kelley11263a42012-04-27 14:00:55 +0100648 if (isprint((int)*r))
Simon Kelley7a14dfe2012-04-20 20:50:42 +0100649 *(q++) = *r;
650 *q = 0;
Simon Kelley42fb8152012-04-20 17:15:01 +0100651
Simon Kelley7a14dfe2012-04-20 20:50:42 +0100652}
Simon Kelley42fb8152012-04-20 17:15:01 +0100653
Simon Kelley832af0b2007-01-21 20:01:28 +0000654static ssize_t tftp_err(int err, char *packet, char *message, char *file)
655{
656 struct errmess {
657 unsigned short op, err;
658 char message[];
659 } *mess = (struct errmess *)packet;
660 ssize_t ret = 4;
661 char *errstr = strerror(errno);
Simon Kelley42fb8152012-04-20 17:15:01 +0100662
663 sanitise(file);
664
Simon Kelley832af0b2007-01-21 20:01:28 +0000665 mess->op = htons(OP_ERR);
666 mess->err = htons(err);
667 ret += (snprintf(mess->message, 500, message, file, errstr) + 1);
Simon Kelley316e2732010-01-22 20:16:09 +0000668 my_syslog(MS_TFTP | LOG_ERR, "%s", mess->message);
Simon Kelley832af0b2007-01-21 20:01:28 +0000669
670 return ret;
671}
672
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000673static ssize_t tftp_err_oops(char *packet, char *file)
674{
Simon Kelley42fb8152012-04-20 17:15:01 +0100675 /* May have >1 refs to file, so potentially mangle a copy of the name */
676 strcpy(daemon->namebuff, file);
677 return tftp_err(ERR_NOTDEF, packet, _("cannot read %s: %s"), daemon->namebuff);
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000678}
679
Simon Kelley832af0b2007-01-21 20:01:28 +0000680/* return -1 for error, zero for done. */
681static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
682{
683 if (transfer->block == 0)
684 {
685 /* send OACK */
686 char *p;
687 struct oackmess {
688 unsigned short op;
689 char data[];
690 } *mess = (struct oackmess *)packet;
691
692 p = mess->data;
693 mess->op = htons(OP_OACK);
694 if (transfer->opt_blocksize)
695 {
696 p += (sprintf(p, "blksize") + 1);
697 p += (sprintf(p, "%d", transfer->blocksize) + 1);
698 }
699 if (transfer->opt_transize)
700 {
701 p += (sprintf(p,"tsize") + 1);
702 p += (sprintf(p, "%u", (unsigned int)transfer->file->size) + 1);
703 }
704
705 return p - packet;
706 }
707 else
708 {
709 /* send data packet */
710 struct datamess {
711 unsigned short op, block;
712 unsigned char data[];
713 } *mess = (struct datamess *)packet;
714
Simon Kelley9e038942008-05-30 20:06:34 +0100715 size_t size = transfer->file->size - transfer->offset;
Simon Kelley832af0b2007-01-21 20:01:28 +0000716
Simon Kelley9e038942008-05-30 20:06:34 +0100717 if (transfer->offset > transfer->file->size)
Simon Kelley832af0b2007-01-21 20:01:28 +0000718 return 0; /* finished */
719
720 if (size > transfer->blocksize)
721 size = transfer->blocksize;
722
Simon Kelley832af0b2007-01-21 20:01:28 +0000723 mess->op = htons(OP_DATA);
724 mess->block = htons((unsigned short)(transfer->block));
725
Simon Kelley9e038942008-05-30 20:06:34 +0100726 if (lseek(transfer->file->fd, transfer->offset, SEEK_SET) == (off_t)-1 ||
727 !read_write(transfer->file->fd, mess->data, size, 1))
Simon Kelley832af0b2007-01-21 20:01:28 +0000728 return -1;
Simon Kelley9e038942008-05-30 20:06:34 +0100729
730 transfer->expansion = 0;
731
732 /* Map '\n' to CR-LF in netascii mode */
733 if (transfer->netascii)
734 {
735 size_t i;
736 int newcarrylf;
737
738 for (i = 0, newcarrylf = 0; i < size; i++)
739 if (mess->data[i] == '\n' && ( i != 0 || !transfer->carrylf))
740 {
Simon Kelley7de060b2011-08-26 17:24:52 +0100741 transfer->expansion++;
742
743 if (size != transfer->blocksize)
Simon Kelley9e038942008-05-30 20:06:34 +0100744 size++; /* room in this block */
Simon Kelley7de060b2011-08-26 17:24:52 +0100745 else if (i == size - 1)
746 newcarrylf = 1; /* don't expand LF again if it moves to the next block */
747
Simon Kelley9e038942008-05-30 20:06:34 +0100748 /* make space and insert CR */
749 memmove(&mess->data[i+1], &mess->data[i], size - (i + 1));
750 mess->data[i] = '\r';
751
752 i++;
753 }
754 transfer->carrylf = newcarrylf;
755
756 }
757
758 return size + 4;
Simon Kelley832af0b2007-01-21 20:01:28 +0000759 }
760}
761
Simon Kelleya9530962012-03-20 22:07:35 +0000762
763int do_tftp_script_run(void)
764{
765 struct tftp_transfer *transfer;
766
767 if ((transfer = daemon->tftp_done_trans))
768 {
769 daemon->tftp_done_trans = transfer->next;
770#ifdef HAVE_SCRIPT
771 queue_tftp(transfer->file->size, transfer->file->filename, &transfer->peer);
772#endif
773 free_transfer(transfer);
774 return 1;
775 }
776
777 return 0;
778}
Simon Kelley832af0b2007-01-21 20:01:28 +0000779#endif