blob: ad5f54461bf00004920774d1d6738be1378814dc [file] [log] [blame]
Simon Kelley28866e92011-02-14 20:19:14 +00001/* dnsmasq is Copyright (c) 2000-2011 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 Kelley8ef5ada2010-06-03 19:42:45 +010021static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, int special);
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);
27
28#define OP_RRQ 1
29#define OP_WRQ 2
30#define OP_DATA 3
31#define OP_ACK 4
32#define OP_ERR 5
33#define OP_OACK 6
34
35#define ERR_NOTDEF 0
36#define ERR_FNF 1
37#define ERR_PERM 2
38#define ERR_FULL 3
39#define ERR_ILL 4
40
Simon Kelley5aabfc72007-08-29 11:24:47 +010041void tftp_request(struct listener *listen, time_t now)
Simon Kelley832af0b2007-01-21 20:01:28 +000042{
43 ssize_t len;
44 char *packet = daemon->packet;
45 char *filename, *mode, *p, *end, *opt;
Simon Kelley28866e92011-02-14 20:19:14 +000046 union mysockaddr addr, peer;
Simon Kelley832af0b2007-01-21 20:01:28 +000047 struct msghdr msg;
Simon Kelley832af0b2007-01-21 20:01:28 +000048 struct iovec iov;
Simon Kelley1f15b812009-10-13 17:49:32 +010049 struct ifreq ifr;
Simon Kelley8ef5ada2010-06-03 19:42:45 +010050 int is_err = 1, if_index = 0, mtu = 0, special = 0;
51#ifdef HAVE_DHCP
Simon Kelley832af0b2007-01-21 20:01:28 +000052 struct iname *tmp;
Simon Kelley8ef5ada2010-06-03 19:42:45 +010053#endif
Simon Kelley5aabfc72007-08-29 11:24:47 +010054 struct tftp_transfer *transfer;
Simon Kelley824af852008-02-12 20:43:05 +000055 int port = daemon->start_tftp_port; /* may be zero to use ephemeral port */
56#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
Simon Kelley1f15b812009-10-13 17:49:32 +010057 int mtuflag = IP_PMTUDISC_DONT;
Simon Kelley824af852008-02-12 20:43:05 +000058#endif
Simon Kelley8ef5ada2010-06-03 19:42:45 +010059 char namebuff[IF_NAMESIZE];
Simon Kelley28866e92011-02-14 20:19:14 +000060 char pretty_addr[ADDRSTRLEN];
Simon Kelley8ef5ada2010-06-03 19:42:45 +010061 char *name;
62 char *prefix = daemon->tftp_prefix;
63 struct tftp_prefix *pref;
64 struct interface_list *ir;
65
Simon Kelley832af0b2007-01-21 20:01:28 +000066 union {
67 struct cmsghdr align; /* this ensures alignment */
Simon Kelley28866e92011-02-14 20:19:14 +000068#ifdef HAVE_IPV6
69 char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
70#endif
Simon Kelley824af852008-02-12 20:43:05 +000071#if defined(HAVE_LINUX_NETWORK)
Simon Kelley832af0b2007-01-21 20:01:28 +000072 char control[CMSG_SPACE(sizeof(struct in_pktinfo))];
Simon Kelley824af852008-02-12 20:43:05 +000073#elif defined(HAVE_SOLARIS_NETWORK)
74 char control[CMSG_SPACE(sizeof(unsigned int))];
Simon Kelley7622fc02009-06-04 20:32:05 +010075#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
Simon Kelley832af0b2007-01-21 20:01:28 +000076 char control[CMSG_SPACE(sizeof(struct sockaddr_dl))];
77#endif
78 } control_u;
79
80 msg.msg_controllen = sizeof(control_u);
81 msg.msg_control = control_u.control;
82 msg.msg_flags = 0;
83 msg.msg_name = &peer;
84 msg.msg_namelen = sizeof(peer);
85 msg.msg_iov = &iov;
86 msg.msg_iovlen = 1;
87
88 iov.iov_base = packet;
89 iov.iov_len = daemon->packet_buff_sz;
90
91 /* we overwrote the buffer... */
92 daemon->srv_save = NULL;
93
94 if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2)
95 return;
96
Simon Kelley28866e92011-02-14 20:19:14 +000097 if (option_bool(OPT_NOWILD))
Simon Kelley1f15b812009-10-13 17:49:32 +010098 {
Simon Kelley28866e92011-02-14 20:19:14 +000099 addr = listen->iface->addr;
Simon Kelley1f15b812009-10-13 17:49:32 +0100100 mtu = listen->iface->mtu;
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100101 name = listen->iface->name;
Simon Kelley1f15b812009-10-13 17:49:32 +0100102 }
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000103 else
104 {
Simon Kelley7622fc02009-06-04 20:32:05 +0100105 struct cmsghdr *cmptr;
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100106 int check;
107 struct interface_list *ir;
108
Simon Kelley28866e92011-02-14 20:19:14 +0000109 if (msg.msg_controllen < sizeof(struct cmsghdr))
110 return;
111
112 addr.sa.sa_family = listen->family;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000113
Simon Kelley832af0b2007-01-21 20:01:28 +0000114#if defined(HAVE_LINUX_NETWORK)
Simon Kelley28866e92011-02-14 20:19:14 +0000115 if (listen->family == AF_INET)
116 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
117 if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO)
118 {
119 union {
120 unsigned char *c;
121 struct in_pktinfo *p;
122 } p;
123 p.c = CMSG_DATA(cmptr);
124 addr.in.sin_addr = p.p->ipi_spec_dst;
125 if_index = p.p->ipi_ifindex;
126 }
127
128#elif defined(HAVE_SOLARIS_NETWORK)
129 if (listen->family == AF_INET)
130 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000131 {
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100132 union {
133 unsigned char *c;
Simon Kelley28866e92011-02-14 20:19:14 +0000134 struct in_addr *a;
135 unsigned int *i;
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100136 } p;
137 p.c = CMSG_DATA(cmptr);
Simon Kelley28866e92011-02-14 20:19:14 +0000138 if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR)
139 addr.in.sin_addr = *(p.a);
140 else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF)
141 if_index = *(p.i);
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000142 }
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000143
Simon Kelley832af0b2007-01-21 20:01:28 +0000144#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
Simon Kelley28866e92011-02-14 20:19:14 +0000145 if (listen->family == AF_INET)
146 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
147 {
148 union {
149 unsigned char *c;
150 struct in_addr *a;
151 struct sockaddr_dl *s;
152 } p;
153 p.c = CMSG_DATA(cmptr);
154 if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR)
155 addr.in.sin_addr = *(p.a);
156 else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF)
157 if_index = p.s->sdl_index;
158 }
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100159
Simon Kelley824af852008-02-12 20:43:05 +0000160#endif
Simon Kelley28866e92011-02-14 20:19:14 +0000161
162#ifdef HAVE_IPV6
163 if (listen->family == AF_INET6)
164 {
165 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
166 if (cmptr->cmsg_level == IPV6_LEVEL && cmptr->cmsg_type == daemon->v6pktinfo)
167 {
168 union {
169 unsigned char *c;
170 struct in6_pktinfo *p;
171 } p;
172 p.c = CMSG_DATA(cmptr);
173
174 addr.in6.sin6_addr = p.p->ipi6_addr;
175 if_index = p.p->ipi6_ifindex;
176 }
177 }
178#endif
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000179
Simon Kelley28866e92011-02-14 20:19:14 +0000180 if (!indextoname(listen->tftpfd, if_index, namebuff))
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000181 return;
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100182
183 name = namebuff;
Simon Kelley28866e92011-02-14 20:19:14 +0000184
185#ifdef HAVE_IPV6
186 if (listen->family == AF_INET6)
187 check = iface_check(AF_INET6, (struct all_addr *)&addr.in6.sin6_addr, name, &if_index);
188 else
189#endif
190 check = iface_check(AF_INET, (struct all_addr *)&addr.in.sin_addr, name, &if_index);
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100191
192 /* wierd TFTP service override */
193 for (ir = daemon->tftp_interfaces; ir; ir = ir->next)
194 if (strcmp(ir->interface, name) == 0)
195 break;
196
197 if (!ir)
198 {
199 if (!daemon->tftp_unlimited || !check)
200 return;
201
202#ifdef HAVE_DHCP
203 /* allowed interfaces are the same as for DHCP */
204 for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
205 if (tmp->name && (strcmp(tmp->name, name) == 0))
206 return;
207#endif
208 }
209
Simon Kelley316e2732010-01-22 20:16:09 +0000210 strncpy(ifr.ifr_name, name, IF_NAMESIZE);
Simon Kelley1f15b812009-10-13 17:49:32 +0100211 if (ioctl(listen->tftpfd, SIOCGIFMTU, &ifr) != -1)
212 mtu = ifr.ifr_mtu;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000213 }
214
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100215 /* check for per-interface prefix */
216 for (pref = daemon->if_prefix; pref; pref = pref->next)
217 if (strcmp(pref->interface, name) == 0)
218 prefix = pref->prefix;
219
220 /* wierd TFTP interfaces disable special options. */
221 for (ir = daemon->tftp_interfaces; ir; ir = ir->next)
222 if (strcmp(ir->interface, name) == 0)
223 special = 1;
224
Simon Kelley832af0b2007-01-21 20:01:28 +0000225#ifdef HAVE_SOCKADDR_SA_LEN
Simon Kelley28866e92011-02-14 20:19:14 +0000226 addr.sa.sa_len = sa_len(&addr);
Simon Kelley832af0b2007-01-21 20:01:28 +0000227#endif
Simon Kelley28866e92011-02-14 20:19:14 +0000228
229 if (listen->family == AF_INET)
230 addr.in.sin_port = htons(port);
231#ifdef HAVE_IPV6
232 else
233 {
234 addr.in6.sin6_port = htons(port);
235 addr.in6.sin6_flowinfo = 0;
236 }
237#endif
238
Simon Kelley5aabfc72007-08-29 11:24:47 +0100239 if (!(transfer = whine_malloc(sizeof(struct tftp_transfer))))
Simon Kelley832af0b2007-01-21 20:01:28 +0000240 return;
241
Simon Kelley28866e92011-02-14 20:19:14 +0000242 if ((transfer->sockfd = socket(listen->family, SOCK_DGRAM, 0)) == -1)
Simon Kelley832af0b2007-01-21 20:01:28 +0000243 {
244 free(transfer);
245 return;
246 }
247
248 transfer->peer = peer;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100249 transfer->timeout = now + 2;
Simon Kelley832af0b2007-01-21 20:01:28 +0000250 transfer->backoff = 1;
251 transfer->block = 1;
252 transfer->blocksize = 512;
Simon Kelley9e038942008-05-30 20:06:34 +0100253 transfer->offset = 0;
Simon Kelley832af0b2007-01-21 20:01:28 +0000254 transfer->file = NULL;
255 transfer->opt_blocksize = transfer->opt_transize = 0;
Simon Kelley9e038942008-05-30 20:06:34 +0100256 transfer->netascii = transfer->carrylf = 0;
Simon Kelley28866e92011-02-14 20:19:14 +0000257
258 prettyprint_addr(&peer, pretty_addr);
259
Simon Kelley824af852008-02-12 20:43:05 +0000260 /* if we have a nailed-down range, iterate until we find a free one. */
261 while (1)
Simon Kelley832af0b2007-01-21 20:01:28 +0000262 {
Simon Kelley28866e92011-02-14 20:19:14 +0000263 if (bind(transfer->sockfd, &addr.sa, sizeof(addr)) == -1 ||
Simon Kelley824af852008-02-12 20:43:05 +0000264#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
Simon Kelley1f15b812009-10-13 17:49:32 +0100265 setsockopt(transfer->sockfd, SOL_IP, IP_MTU_DISCOVER, &mtuflag, sizeof(mtuflag)) == -1 ||
Simon Kelley824af852008-02-12 20:43:05 +0000266#endif
267 !fix_fd(transfer->sockfd))
268 {
269 if (errno == EADDRINUSE && daemon->start_tftp_port != 0)
270 {
271 if (++port <= daemon->end_tftp_port)
272 {
Simon Kelley28866e92011-02-14 20:19:14 +0000273 if (listen->family == AF_INET)
274 addr.in.sin_port = htons(port);
275#ifdef HAVE_IPV6
276 else
277 addr.in6.sin6_port = htons(port);
278#endif
Simon Kelley824af852008-02-12 20:43:05 +0000279 continue;
280 }
Simon Kelley7622fc02009-06-04 20:32:05 +0100281 my_syslog(MS_TFTP | LOG_ERR, _("unable to get free port for TFTP"));
Simon Kelley824af852008-02-12 20:43:05 +0000282 }
283 free_transfer(transfer);
284 return;
285 }
286 break;
Simon Kelley832af0b2007-01-21 20:01:28 +0000287 }
Simon Kelley824af852008-02-12 20:43:05 +0000288
Simon Kelley832af0b2007-01-21 20:01:28 +0000289 p = packet + 2;
290 end = packet + len;
291
292 if (ntohs(*((unsigned short *)packet)) != OP_RRQ ||
293 !(filename = next(&p, end)) ||
294 !(mode = next(&p, end)) ||
Simon Kelley9e038942008-05-30 20:06:34 +0100295 (strcasecmp(mode, "octet") != 0 && strcasecmp(mode, "netascii") != 0))
Simon Kelley28866e92011-02-14 20:19:14 +0000296 len = tftp_err(ERR_ILL, packet, _("unsupported request from %s"), pretty_addr);
Simon Kelley832af0b2007-01-21 20:01:28 +0000297 else
298 {
Simon Kelley9e038942008-05-30 20:06:34 +0100299 if (strcasecmp(mode, "netascii") == 0)
300 transfer->netascii = 1;
301
Simon Kelley832af0b2007-01-21 20:01:28 +0000302 while ((opt = next(&p, end)))
303 {
Simon Kelley77e94da2009-08-31 17:32:17 +0100304 if (strcasecmp(opt, "blksize") == 0)
Simon Kelley832af0b2007-01-21 20:01:28 +0000305 {
Simon Kelley77e94da2009-08-31 17:32:17 +0100306 if ((opt = next(&p, end)) &&
Simon Kelley28866e92011-02-14 20:19:14 +0000307 (special || !option_bool(OPT_TFTP_NOBLOCK)))
Simon Kelley77e94da2009-08-31 17:32:17 +0100308 {
309 transfer->blocksize = atoi(opt);
310 if (transfer->blocksize < 1)
311 transfer->blocksize = 1;
312 if (transfer->blocksize > (unsigned)daemon->packet_buff_sz - 4)
313 transfer->blocksize = (unsigned)daemon->packet_buff_sz - 4;
Simon Kelley1f15b812009-10-13 17:49:32 +0100314 /* 32 bytes for IP, UDP and TFTP headers */
315 if (mtu != 0 && transfer->blocksize > (unsigned)mtu - 32)
316 transfer->blocksize = (unsigned)mtu - 32;
Simon Kelley77e94da2009-08-31 17:32:17 +0100317 transfer->opt_blocksize = 1;
318 transfer->block = 0;
319 }
Simon Kelley832af0b2007-01-21 20:01:28 +0000320 }
Simon Kelley77e94da2009-08-31 17:32:17 +0100321 else if (strcasecmp(opt, "tsize") == 0 && next(&p, end) && !transfer->netascii)
Simon Kelley832af0b2007-01-21 20:01:28 +0000322 {
323 transfer->opt_transize = 1;
324 transfer->block = 0;
325 }
326 }
327
Simon Kelley1f15b812009-10-13 17:49:32 +0100328 /* cope with backslashes from windows boxen. */
329 while ((p = strchr(filename, '\\')))
330 *p = '/';
331
Simon Kelleyf2621c72007-04-29 19:47:21 +0100332 strcpy(daemon->namebuff, "/");
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100333 if (prefix)
Simon Kelley832af0b2007-01-21 20:01:28 +0000334 {
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100335 if (prefix[0] == '/')
Simon Kelleyf2621c72007-04-29 19:47:21 +0100336 daemon->namebuff[0] = 0;
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100337 strncat(daemon->namebuff, prefix, (MAXDNAME-1) - strlen(daemon->namebuff));
338 if (prefix[strlen(prefix)-1] != '/')
Simon Kelley77e94da2009-08-31 17:32:17 +0100339 strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff));
Simon Kelley5aabfc72007-08-29 11:24:47 +0100340
Simon Kelley28866e92011-02-14 20:19:14 +0000341 if (!special && option_bool(OPT_TFTP_APREF))
Simon Kelley5aabfc72007-08-29 11:24:47 +0100342 {
343 size_t oldlen = strlen(daemon->namebuff);
344 struct stat statbuf;
345
Simon Kelley28866e92011-02-14 20:19:14 +0000346 strncat(daemon->namebuff, pretty_addr, (MAXDNAME-1) - strlen(daemon->namebuff));
Simon Kelley77e94da2009-08-31 17:32:17 +0100347 strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff));
Simon Kelley5aabfc72007-08-29 11:24:47 +0100348
349 /* remove unique-directory if it doesn't exist */
350 if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode))
351 daemon->namebuff[oldlen] = 0;
352 }
353
Simon Kelleyf2621c72007-04-29 19:47:21 +0100354 /* Absolute pathnames OK if they match prefix */
355 if (filename[0] == '/')
356 {
357 if (strstr(filename, daemon->namebuff) == filename)
358 daemon->namebuff[0] = 0;
359 else
360 filename++;
361 }
Simon Kelley832af0b2007-01-21 20:01:28 +0000362 }
Simon Kelleyf2621c72007-04-29 19:47:21 +0100363 else if (filename[0] == '/')
Simon Kelley832af0b2007-01-21 20:01:28 +0000364 daemon->namebuff[0] = 0;
Simon Kelley77e94da2009-08-31 17:32:17 +0100365 strncat(daemon->namebuff, filename, (MAXDNAME-1) - strlen(daemon->namebuff));
Simon Kelley832af0b2007-01-21 20:01:28 +0000366
Simon Kelley5aabfc72007-08-29 11:24:47 +0100367 /* check permissions and open file */
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100368 if ((transfer->file = check_tftp_fileperm(&len, prefix, special)))
Simon Kelley832af0b2007-01-21 20:01:28 +0000369 {
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000370 if ((len = get_block(packet, transfer)) == -1)
371 len = tftp_err_oops(packet, daemon->namebuff);
372 else
373 is_err = 0;
Simon Kelley832af0b2007-01-21 20:01:28 +0000374 }
375 }
376
377 while (sendto(transfer->sockfd, packet, len, 0,
378 (struct sockaddr *)&peer, sizeof(peer)) == -1 && errno == EINTR);
379
380 if (is_err)
381 free_transfer(transfer);
382 else
383 {
Simon Kelley832af0b2007-01-21 20:01:28 +0000384 transfer->next = daemon->tftp_trans;
385 daemon->tftp_trans = transfer;
386 }
387}
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000388
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100389static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix, int special)
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000390{
391 char *packet = daemon->packet, *namebuff = daemon->namebuff;
392 struct tftp_file *file;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100393 struct tftp_transfer *t;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000394 uid_t uid = geteuid();
395 struct stat statbuf;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100396 int fd = -1;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000397
398 /* trick to ban moving out of the subtree */
Simon Kelley8ef5ada2010-06-03 19:42:45 +0100399 if (prefix && strstr(namebuff, "/../"))
Simon Kelley5aabfc72007-08-29 11:24:47 +0100400 goto perm;
401
402 if ((fd = open(namebuff, O_RDONLY)) == -1)
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000403 {
Simon Kelley5aabfc72007-08-29 11:24:47 +0100404 if (errno == ENOENT)
405 {
406 *len = tftp_err(ERR_FNF, packet, _("file %s not found"), namebuff);
407 return NULL;
408 }
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000409 else if (errno == EACCES)
410 goto perm;
411 else
412 goto oops;
413 }
Simon Kelley5aabfc72007-08-29 11:24:47 +0100414
415 /* stat the file descriptor to avoid stat->open races */
416 if (fstat(fd, &statbuf) == -1)
417 goto oops;
418
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000419 /* running as root, must be world-readable */
420 if (uid == 0)
421 {
422 if (!(statbuf.st_mode & S_IROTH))
Simon Kelley5aabfc72007-08-29 11:24:47 +0100423 goto perm;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000424 }
425 /* in secure mode, must be owned by user running dnsmasq */
Simon Kelley28866e92011-02-14 20:19:14 +0000426 else if (!special && option_bool(OPT_TFTP_SECURE) && uid != statbuf.st_uid)
Simon Kelley5aabfc72007-08-29 11:24:47 +0100427 goto perm;
428
429 /* If we're doing many tranfers from the same file, only
430 open it once this saves lots of file descriptors
431 when mass-booting a big cluster, for instance.
432 Be conservative and only share when inode and name match
433 this keeps error messages sane. */
434 for (t = daemon->tftp_trans; t; t = t->next)
435 if (t->file->dev == statbuf.st_dev &&
436 t->file->inode == statbuf.st_ino &&
437 strcmp(t->file->filename, namebuff) == 0)
438 {
439 close(fd);
440 t->file->refcount++;
441 return t->file;
442 }
443
444 if (!(file = whine_malloc(sizeof(struct tftp_file) + strlen(namebuff) + 1)))
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000445 {
446 errno = ENOMEM;
447 goto oops;
448 }
449
Simon Kelley5aabfc72007-08-29 11:24:47 +0100450 file->fd = fd;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000451 file->size = statbuf.st_size;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100452 file->dev = statbuf.st_dev;
453 file->inode = statbuf.st_ino;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000454 file->refcount = 1;
455 strcpy(file->filename, namebuff);
456 return file;
Simon Kelley5aabfc72007-08-29 11:24:47 +0100457
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000458 perm:
Simon Kelley5aabfc72007-08-29 11:24:47 +0100459 errno = EACCES;
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000460 *len = tftp_err(ERR_PERM, packet, _("cannot access %s: %s"), namebuff);
Simon Kelley5aabfc72007-08-29 11:24:47 +0100461 if (fd != -1)
462 close(fd);
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000463 return NULL;
464
Simon Kelley5aabfc72007-08-29 11:24:47 +0100465 oops:
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000466 *len = tftp_err_oops(packet, namebuff);
Simon Kelley5aabfc72007-08-29 11:24:47 +0100467 if (fd != -1)
468 close(fd);
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000469 return NULL;
470}
471
Simon Kelley5aabfc72007-08-29 11:24:47 +0100472void check_tftp_listeners(fd_set *rset, time_t now)
Simon Kelley832af0b2007-01-21 20:01:28 +0000473{
474 struct tftp_transfer *transfer, *tmp, **up;
475 ssize_t len;
Simon Kelley28866e92011-02-14 20:19:14 +0000476 char pretty_addr[ADDRSTRLEN];
Simon Kelley832af0b2007-01-21 20:01:28 +0000477
478 struct ack {
479 unsigned short op, block;
480 } *mess = (struct ack *)daemon->packet;
481
482 /* Check for activity on any existing transfers */
483 for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp)
484 {
485 tmp = transfer->next;
486
487 if (FD_ISSET(transfer->sockfd, rset))
488 {
489 /* we overwrote the buffer... */
490 daemon->srv_save = NULL;
Simon Kelley28866e92011-02-14 20:19:14 +0000491
492 prettyprint_addr(&transfer->peer, pretty_addr);
Simon Kelley832af0b2007-01-21 20:01:28 +0000493
494 if ((len = recv(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0)) >= (ssize_t)sizeof(struct ack))
495 {
496 if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block)
497 {
498 /* Got ack, ensure we take the (re)transmit path */
499 transfer->timeout = now;
500 transfer->backoff = 0;
Simon Kelley9e038942008-05-30 20:06:34 +0100501 if (transfer->block++ != 0)
502 transfer->offset += transfer->blocksize - transfer->expansion;
Simon Kelley832af0b2007-01-21 20:01:28 +0000503 }
504 else if (ntohs(mess->op) == OP_ERR)
505 {
506 char *p = daemon->packet + sizeof(struct ack);
507 char *end = daemon->packet + len;
508 char *err = next(&p, end);
Simon Kelley28866e92011-02-14 20:19:14 +0000509
Simon Kelley832af0b2007-01-21 20:01:28 +0000510 /* Sanitise error message */
511 if (!err)
512 err = "";
513 else
514 {
515 char *q, *r;
516 for (q = r = err; *r; r++)
Simon Kelley824af852008-02-12 20:43:05 +0000517 if (isprint((int)*r))
Simon Kelley832af0b2007-01-21 20:01:28 +0000518 *(q++) = *r;
519 *q = 0;
520 }
Simon Kelley28866e92011-02-14 20:19:14 +0000521
Simon Kelley316e2732010-01-22 20:16:09 +0000522 my_syslog(MS_TFTP | LOG_ERR, _("error %d %s received from %s"),
Simon Kelleyf2621c72007-04-29 19:47:21 +0100523 (int)ntohs(mess->block), err,
Simon Kelley28866e92011-02-14 20:19:14 +0000524 pretty_addr);
Simon Kelley832af0b2007-01-21 20:01:28 +0000525
526 /* Got err, ensure we take abort */
527 transfer->timeout = now;
528 transfer->backoff = 100;
529 }
530 }
531 }
532
533 if (difftime(now, transfer->timeout) >= 0.0)
534 {
535 int endcon = 0;
536
537 /* timeout, retransmit */
Simon Kelley5aabfc72007-08-29 11:24:47 +0100538 transfer->timeout += 1 + (1<<transfer->backoff);
Simon Kelley832af0b2007-01-21 20:01:28 +0000539
540 /* we overwrote the buffer... */
541 daemon->srv_save = NULL;
542
543 if ((len = get_block(daemon->packet, transfer)) == -1)
544 {
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000545 len = tftp_err_oops(daemon->packet, transfer->file->filename);
Simon Kelley832af0b2007-01-21 20:01:28 +0000546 endcon = 1;
547 }
548 else if (++transfer->backoff > 5)
549 {
550 /* don't complain about timeout when we're awaiting the last
551 ACK, some clients never send it */
552 if (len != 0)
Simon Kelley28866e92011-02-14 20:19:14 +0000553 {
554 my_syslog(MS_TFTP | LOG_ERR, _("failed sending %s to %s"),
555 transfer->file->filename, pretty_addr);
556 len = 0;
557 endcon = 1;
558 }
Simon Kelley832af0b2007-01-21 20:01:28 +0000559 }
560
561 if (len != 0)
562 while(sendto(transfer->sockfd, daemon->packet, len, 0,
563 (struct sockaddr *)&transfer->peer, sizeof(transfer->peer)) == -1 && errno == EINTR);
564
565 if (endcon || len == 0)
566 {
Simon Kelley28866e92011-02-14 20:19:14 +0000567 if (!endcon)
568 my_syslog(MS_TFTP | LOG_INFO, _("sent %s to %s"), transfer->file->filename, pretty_addr);
Simon Kelley832af0b2007-01-21 20:01:28 +0000569 /* unlink */
570 *up = tmp;
571 free_transfer(transfer);
572 continue;
573 }
574 }
575
576 up = &transfer->next;
Simon Kelley28866e92011-02-14 20:19:14 +0000577 }
Simon Kelley832af0b2007-01-21 20:01:28 +0000578}
579
580static void free_transfer(struct tftp_transfer *transfer)
581{
582 close(transfer->sockfd);
583 if (transfer->file && (--transfer->file->refcount) == 0)
584 {
585 close(transfer->file->fd);
586 free(transfer->file);
587 }
588 free(transfer);
589}
590
591static char *next(char **p, char *end)
592{
593 char *ret = *p;
594 size_t len;
595
596 if (*(end-1) != 0 ||
597 *p == end ||
598 (len = strlen(ret)) == 0)
599 return NULL;
600
601 *p += len + 1;
602 return ret;
603}
604
605static ssize_t tftp_err(int err, char *packet, char *message, char *file)
606{
607 struct errmess {
608 unsigned short op, err;
609 char message[];
610 } *mess = (struct errmess *)packet;
611 ssize_t ret = 4;
612 char *errstr = strerror(errno);
613
614 mess->op = htons(OP_ERR);
615 mess->err = htons(err);
616 ret += (snprintf(mess->message, 500, message, file, errstr) + 1);
Simon Kelley316e2732010-01-22 20:16:09 +0000617 my_syslog(MS_TFTP | LOG_ERR, "%s", mess->message);
Simon Kelley832af0b2007-01-21 20:01:28 +0000618
619 return ret;
620}
621
Simon Kelley1b7ecd12007-02-05 14:57:57 +0000622static ssize_t tftp_err_oops(char *packet, char *file)
623{
624 return tftp_err(ERR_NOTDEF, packet, _("cannot read %s: %s"), file);
625}
626
Simon Kelley832af0b2007-01-21 20:01:28 +0000627/* return -1 for error, zero for done. */
628static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
629{
630 if (transfer->block == 0)
631 {
632 /* send OACK */
633 char *p;
634 struct oackmess {
635 unsigned short op;
636 char data[];
637 } *mess = (struct oackmess *)packet;
638
639 p = mess->data;
640 mess->op = htons(OP_OACK);
641 if (transfer->opt_blocksize)
642 {
643 p += (sprintf(p, "blksize") + 1);
644 p += (sprintf(p, "%d", transfer->blocksize) + 1);
645 }
646 if (transfer->opt_transize)
647 {
648 p += (sprintf(p,"tsize") + 1);
649 p += (sprintf(p, "%u", (unsigned int)transfer->file->size) + 1);
650 }
651
652 return p - packet;
653 }
654 else
655 {
656 /* send data packet */
657 struct datamess {
658 unsigned short op, block;
659 unsigned char data[];
660 } *mess = (struct datamess *)packet;
661
Simon Kelley9e038942008-05-30 20:06:34 +0100662 size_t size = transfer->file->size - transfer->offset;
Simon Kelley832af0b2007-01-21 20:01:28 +0000663
Simon Kelley9e038942008-05-30 20:06:34 +0100664 if (transfer->offset > transfer->file->size)
Simon Kelley832af0b2007-01-21 20:01:28 +0000665 return 0; /* finished */
666
667 if (size > transfer->blocksize)
668 size = transfer->blocksize;
669
Simon Kelley832af0b2007-01-21 20:01:28 +0000670 mess->op = htons(OP_DATA);
671 mess->block = htons((unsigned short)(transfer->block));
672
Simon Kelley9e038942008-05-30 20:06:34 +0100673 if (lseek(transfer->file->fd, transfer->offset, SEEK_SET) == (off_t)-1 ||
674 !read_write(transfer->file->fd, mess->data, size, 1))
Simon Kelley832af0b2007-01-21 20:01:28 +0000675 return -1;
Simon Kelley9e038942008-05-30 20:06:34 +0100676
677 transfer->expansion = 0;
678
679 /* Map '\n' to CR-LF in netascii mode */
680 if (transfer->netascii)
681 {
682 size_t i;
683 int newcarrylf;
684
685 for (i = 0, newcarrylf = 0; i < size; i++)
686 if (mess->data[i] == '\n' && ( i != 0 || !transfer->carrylf))
687 {
688 if (size == transfer->blocksize)
689 {
690 transfer->expansion++;
691 if (i == size - 1)
692 newcarrylf = 1; /* don't expand LF again if it moves to the next block */
693 }
694 else
695 size++; /* room in this block */
696
697 /* make space and insert CR */
698 memmove(&mess->data[i+1], &mess->data[i], size - (i + 1));
699 mess->data[i] = '\r';
700
701 i++;
702 }
703 transfer->carrylf = newcarrylf;
704
705 }
706
707 return size + 4;
Simon Kelley832af0b2007-01-21 20:01:28 +0000708 }
709}
710
711#endif