/* dnsmasq is Copyright (c) 2000-2025 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 dated June, 1991, or (at your option) version 3 dated 29 June, 2007. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "dnsmasq.h" #ifdef HAVE_TFTP static void handle_tftp(char *packet, time_t now, struct tftp_transfer *transfer, ssize_t len); static struct tftp_file *check_tftp_fileperm(char *packet, ssize_t *len, char *prefix, char *client); static void free_transfer(struct tftp_transfer *transfer); static ssize_t tftp_err(int err, char *packet, char *message, char *file, char *arg2); static ssize_t tftp_err_oops(char *packet, const char *file); static ssize_t get_block(struct tftp_transfer *transfer); static char *next(char **p, char *end); static void sanitise(char *buf); #define OP_RRQ 1 #define OP_WRQ 2 #define OP_DATA 3 #define OP_ACK 4 #define OP_ERR 5 #define OP_OACK 6 #define ERR_NOTDEF 0 #define ERR_FNF 1 #define ERR_PERM 2 #define ERR_FULL 3 #define ERR_ILL 4 #define ERR_TID 5 static void tftp_request(char *packet, ssize_t plen, struct listener *listen, time_t now) { ssize_t len; char *filename, *mode, *p, *end; union mysockaddr addr, peer; struct msghdr msg; struct iovec iov; struct ifreq ifr; int is_err = 1, if_index = 0, mtu = 0; struct iname *tmp; struct tftp_transfer *transfer = NULL, **up; int port = daemon->start_tftp_port; /* may be zero to use ephemeral port */ #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) int mtuflag = IP_PMTUDISC_DONT; #endif char namebuff[IF_NAMESIZE]; char *name = NULL; char *prefix = daemon->tftp_prefix; struct tftp_prefix *pref; union all_addr addra; int family = listen->addr.sa.sa_family; /* Can always get recvd interface for IPv6 */ int check_dest = !option_bool(OPT_NOWILD) || family == AF_INET6; union { struct cmsghdr align; /* this ensures alignment */ char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; #if defined(HAVE_LINUX_NETWORK) char control[CMSG_SPACE(sizeof(struct in_pktinfo))]; #elif defined(HAVE_SOLARIS_NETWORK) char control[CMSG_SPACE(sizeof(struct in_addr)) + CMSG_SPACE(sizeof(unsigned int))]; #elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) char control[CMSG_SPACE(sizeof(struct in_addr)) + CMSG_SPACE(sizeof(struct sockaddr_dl))]; #endif } control_u; msg.msg_controllen = sizeof(control_u); msg.msg_control = control_u.control; msg.msg_flags = 0; msg.msg_name = &peer; msg.msg_namelen = sizeof(peer); msg.msg_iov = &iov; msg.msg_iovlen = 1; /* packet buff is DNS name workspace. */ iov.iov_base = packet; iov.iov_len = plen; if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2) return; #ifdef HAVE_DUMPFILE dump_packet_udp(DUMP_TFTP, (void *)packet, len, (union mysockaddr *)&peer, NULL, listen->tftpfd); #endif /* Can always get recvd interface for IPv6 */ if (!check_dest) { if (listen->iface) { addr = listen->iface->addr; name = listen->iface->name; mtu = listen->iface->mtu; if (daemon->tftp_mtu != 0 && daemon->tftp_mtu < mtu) mtu = daemon->tftp_mtu; } else { /* we're listening on an address that doesn't appear on an interface, ask the kernel what the socket is bound to */ socklen_t tcp_len = sizeof(union mysockaddr); if (getsockname(listen->tftpfd, (struct sockaddr *)&addr, &tcp_len) == -1) return; } } else { struct cmsghdr *cmptr; if (msg.msg_controllen < sizeof(struct cmsghdr)) return; addr.sa.sa_family = family; #if defined(HAVE_LINUX_NETWORK) if (family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) { union { unsigned char *c; struct in_pktinfo *p; } p; p.c = CMSG_DATA(cmptr); addr.in.sin_addr = p.p->ipi_spec_dst; if_index = p.p->ipi_ifindex; } #elif defined(HAVE_SOLARIS_NETWORK) if (family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) { union { unsigned char *c; struct in_addr *a; unsigned int *i; } p; p.c = CMSG_DATA(cmptr); if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR) addr.in.sin_addr = *(p.a); else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF) if_index = *(p.i); } #elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF) if (family == AF_INET) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) { union { unsigned char *c; struct in_addr *a; struct sockaddr_dl *s; } p; p.c = CMSG_DATA(cmptr); if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR) addr.in.sin_addr = *(p.a); else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF) if_index = p.s->sdl_index; } #endif if (family == AF_INET6) { for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) { union { unsigned char *c; struct in6_pktinfo *p; } p; p.c = CMSG_DATA(cmptr); addr.in6.sin6_addr = p.p->ipi6_addr; if_index = p.p->ipi6_ifindex; } } if (!indextoname(listen->tftpfd, if_index, namebuff)) return; name = namebuff; if (family == AF_INET6) addra.addr6 = addr.in6.sin6_addr; else addra.addr4 = addr.in.sin_addr; if (daemon->tftp_interfaces) { /* dedicated tftp interface list */ for (tmp = daemon->tftp_interfaces; tmp; tmp = tmp->next) if (tmp->name && wildcard_match(tmp->name, name)) break; if (!tmp) return; } else { /* Do the same as DHCP */ if (!iface_check(family, &addra, name, NULL)) { if (!option_bool(OPT_CLEVERBIND)) enumerate_interfaces(0); if (!loopback_exception(listen->tftpfd, family, &addra, name) && !label_exception(if_index, family, &addra)) return; } #ifdef HAVE_DHCP /* allowed interfaces are the same as for DHCP */ for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) if (tmp->name && (tmp->flags & INAME_4) && (tmp->flags & INAME_6) && wildcard_match(tmp->name, name)) return; #endif } safe_strncpy(ifr.ifr_name, name, IF_NAMESIZE); if (ioctl(listen->tftpfd, SIOCGIFMTU, &ifr) != -1) { mtu = ifr.ifr_mtu; if (daemon->tftp_mtu != 0 && daemon->tftp_mtu < mtu) mtu = daemon->tftp_mtu; } } /* Failed to get interface mtu - can use configured value. */ if (mtu == 0) mtu = daemon->tftp_mtu; /* data transfer via server listening socket */ if (option_bool(OPT_SINGLE_PORT)) { int tftp_cnt; for (tftp_cnt = 0, transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; up = &transfer->next, transfer = transfer->next) { tftp_cnt++; if (sockaddr_isequal(&peer, &transfer->peer)) { if (ntohs(*((unsigned short *)packet)) == OP_RRQ) { /* Handle repeated RRQ or abandoned transfer from same host and port by unlinking and reusing the struct transfer. */ *up = transfer->next; break; } else { handle_tftp(packet, now, transfer, len); return; } } } /* Enforce simultaneous transfer limit. In non-single-port mode this is done by not listening on the server socket when too many transfers are in progress. */ if (!transfer && tftp_cnt >= daemon->tftp_max) return; } if (name) { /* check for per-interface prefix */ for (pref = daemon->if_prefix; pref; pref = pref->next) if (strcmp(pref->interface, name) == 0) prefix = pref->prefix; } if (family == AF_INET) { addr.in.sin_port = htons(port); #ifdef HAVE_SOCKADDR_SA_LEN addr.in.sin_len = sizeof(addr.in); #endif } else { addr.in6.sin6_port = htons(port); addr.in6.sin6_flowinfo = 0; addr.in6.sin6_scope_id = 0; #ifdef HAVE_SOCKADDR_SA_LEN addr.in6.sin6_len = sizeof(addr.in6); #endif } /* May reuse struct transfer from abandoned transfer in single port mode. */ if (!transfer && !(transfer = whine_malloc(sizeof(struct tftp_transfer)))) return; memset(transfer, 0, sizeof(struct tftp_transfer)); if (option_bool(OPT_SINGLE_PORT)) transfer->sockfd = listen->tftpfd; else if ((transfer->sockfd = socket(family, SOCK_DGRAM, 0)) == -1) { free(transfer); return; } transfer->peer = peer; transfer->source = addra; transfer->if_index = if_index; transfer->timeout = 2; transfer->start = now; transfer->backoff = 1; transfer->block = 1; transfer->ackprev = 0; transfer->block_hi = 0; transfer->blocksize = 512; transfer->windowsize = 1; (void)prettyprint_addr(&peer, daemon->addrbuff); /* if we have a nailed-down range, iterate until we find a free one. */ while (!option_bool(OPT_SINGLE_PORT)) { if (bind(transfer->sockfd, &addr.sa, sa_len(&addr)) == -1 || #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) setsockopt(transfer->sockfd, IPPROTO_IP, IP_MTU_DISCOVER, &mtuflag, sizeof(mtuflag)) == -1 || #endif !fix_fd(transfer->sockfd)) { if (errno == EADDRINUSE && daemon->start_tftp_port != 0) { if (++port <= daemon->end_tftp_port) { if (family == AF_INET) addr.in.sin_port = htons(port); else addr.in6.sin6_port = htons(port); continue; } my_syslog(MS_TFTP | LOG_ERR, _("unable to get free port for TFTP")); } free_transfer(transfer); return; } break; } p = packet + 2; end = packet + len; len = 0; if (ntohs(*((unsigned short *)packet)) == OP_WRQ) len = tftp_err(ERR_ILL, packet, _("unsupported write request from %s"),daemon->addrbuff, NULL); else if (ntohs(*((unsigned short *)packet)) == OP_RRQ) { if (!(filename = next(&p, end))) len = tftp_err(ERR_ILL, packet, _("empty filename in request from %s"), daemon->addrbuff, NULL); else if (!(mode = next(&p, end)) || (strcasecmp(mode, "octet") != 0 && strcasecmp(mode, "netascii") != 0)) len = tftp_err(ERR_ILL, packet, _("unsupported request from %s"),daemon->addrbuff, NULL); else { char *opt, *arg; if (strcasecmp(mode, "netascii") == 0) transfer->netascii = 1; while ((opt = next(&p, end)) && (arg = next(&p, end))) { unsigned int val = atoi(arg); if (strcasecmp(opt, "blksize") == 0 && !option_bool(OPT_TFTP_NOBLOCK)) { /* 32 bytes for IP, UDP and TFTP headers, 52 bytes for IPv6 */ int overhead = (family == AF_INET) ? 32 : 52; if (val < 1) val = 1; if (val > (unsigned)daemon->packet_buff_sz - 4) val = (unsigned)daemon->packet_buff_sz - 4; if (mtu != 0 && val > (unsigned)mtu - overhead) val = (unsigned)mtu - overhead; transfer->blocksize = val; transfer->opt_blocksize = 1; transfer->block = 0; } else if (strcasecmp(opt, "tsize") == 0 && !transfer->netascii) { transfer->opt_transize = 1; transfer->block = 0; } else if (strcasecmp(opt, "timeout") == 0) { if (val > 255) val = 255; transfer->timeout = val; transfer->opt_timeout = 1; transfer->block = 0; } else if (strcasecmp(opt, "windowsize") == 0 && !transfer->netascii) { /* windowsize option only supported for binary transfers. */ if (val < 1) val = 1; if (val > TFTP_MAX_WINDOW) val = TFTP_MAX_WINDOW; transfer->windowsize = val; transfer->opt_windowsize = 1; transfer->block = 0; } } /* cope with backslashes from windows boxen. */ for (p = filename; *p; p++) if (*p == '\\') *p = '/'; else if (option_bool(OPT_TFTP_LC)) *p = tolower((unsigned char)*p); strcpy(daemon->namebuff, "/"); if (prefix) { if (prefix[0] == '/') daemon->namebuff[0] = 0; strncat(daemon->namebuff, prefix, (MAXDNAME-1) - strlen(daemon->namebuff)); if (prefix[strlen(prefix)-1] != '/') strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff)); if (option_bool(OPT_TFTP_APREF_IP)) { size_t oldlen = strlen(daemon->namebuff); struct stat statbuf; strncat(daemon->namebuff, daemon->addrbuff, (MAXDNAME-1) - strlen(daemon->namebuff)); strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff)); /* remove unique-directory if it doesn't exist */ if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode)) daemon->namebuff[oldlen] = 0; } if (option_bool(OPT_TFTP_APREF_MAC)) { unsigned char *macaddr = NULL; unsigned char macbuf[DHCP_CHADDR_MAX]; #ifdef HAVE_DHCP if (daemon->dhcp && peer.sa.sa_family == AF_INET) { /* Check if the client IP is in our lease database */ struct dhcp_lease *lease = lease_find_by_addr(peer.in.sin_addr); if (lease && lease->hwaddr_type == ARPHRD_ETHER && lease->hwaddr_len == ETHER_ADDR_LEN) macaddr = lease->hwaddr; } #endif /* If no luck, try to find in ARP table. This only works if client is in same (V)LAN */ if (!macaddr && find_mac(&peer, macbuf, 1, now) > 0) macaddr = macbuf; if (macaddr) { size_t oldlen = strlen(daemon->namebuff); struct stat statbuf; snprintf(daemon->namebuff + oldlen, (MAXDNAME-1) - oldlen, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x/", macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]); /* remove unique-directory if it doesn't exist */ if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode)) daemon->namebuff[oldlen] = 0; } } /* Absolute pathnames OK if they match prefix */ if (filename[0] == '/') { if (strstr(filename, daemon->namebuff) == filename) daemon->namebuff[0] = 0; else filename++; } } else if (filename[0] == '/') daemon->namebuff[0] = 0; strncat(daemon->namebuff, filename, (MAXDNAME-1) - strlen(daemon->namebuff)); /* check permissions and open file */ if ((transfer->file = check_tftp_fileperm(packet, &len, prefix, daemon->addrbuff))) { transfer->lastack = transfer->block; transfer->retransmit = now + transfer->timeout; /* This packet is may be the first data packet, but only if windowsize == 1 To get windowsize greater then one requires an option negotiation, in which case this packet is the OACK. */ if ((len = get_block(transfer)) == -1) len = tftp_err_oops(packet, daemon->namebuff); else { is_err = 0; /* get_block put the packet to send in a different buffer. */ packet = daemon->packet; } } } } if (len) { send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), packet, len, &peer, &addra, if_index); #ifdef HAVE_DUMPFILE dump_packet_udp(DUMP_TFTP, (void *)packet, len, NULL, (union mysockaddr *)&peer, transfer->sockfd); #endif } if (is_err) free_transfer(transfer); else { transfer->next = daemon->tftp_trans; daemon->tftp_trans = transfer; } } static struct tftp_file *check_tftp_fileperm(char *packet, ssize_t *len, char *prefix, char *client) { char *namebuff = daemon->namebuff; struct tftp_file *file; struct tftp_transfer *t; uid_t uid = geteuid(); struct stat statbuf; int fd = -1; /* trick to ban moving out of the subtree */ if (prefix && strstr(namebuff, "/../")) goto perm; if ((fd = open(namebuff, O_RDONLY)) == -1) { if (errno == ENOENT) { *len = tftp_err(ERR_FNF, packet, _("file %s not found for %s"), namebuff, client); return NULL; } else if (errno == EACCES) goto perm; else goto oops; } /* stat the file descriptor to avoid stat->open races */ if (fstat(fd, &statbuf) == -1) goto oops; /* running as root, must be world-readable */ if (uid == 0) { if (!(statbuf.st_mode & S_IROTH)) goto perm; } /* in secure mode, must be owned by user running dnsmasq */ else if (option_bool(OPT_TFTP_SECURE) && uid != statbuf.st_uid) goto perm; /* If we're doing many transfers from the same file, only open it once this saves lots of file descriptors when mass-booting a big cluster, for instance. Be conservative and only share when inode and name match this keeps error messages sane. */ for (t = daemon->tftp_trans; t; t = t->next) if (t->file->dev == statbuf.st_dev && t->file->inode == statbuf.st_ino && strcmp(t->file->filename, namebuff) == 0) { close(fd); t->file->refcount++; return t->file; } if (!(file = whine_malloc(sizeof(struct tftp_file) + strlen(namebuff) + 1))) { errno = ENOMEM; goto oops; } file->fd = fd; file->size = statbuf.st_size; file->dev = statbuf.st_dev; file->inode = statbuf.st_ino; file->posn = 0; file->refcount = 1; strcpy(file->filename, namebuff); return file; perm: *len = tftp_err(ERR_PERM, packet, _("cannot access %s: %s"), namebuff, strerror(EACCES)); if (fd != -1) close(fd); return NULL; oops: *len = tftp_err_oops(packet, namebuff); if (fd != -1) close(fd); return NULL; } void check_tftp_listeners(time_t now) { /* Use workspace to receive (small) request/ACK, to avoid overwriting precomputed reply */ char *packet = daemon->workspacename; ssize_t plen = MAXDNAME * 2; struct listener *listener; struct tftp_transfer *transfer, *tmp, **up; for (listener = daemon->listeners; listener; listener = listener->next) if (listener->tftpfd != -1 && poll_check(listener->tftpfd, POLLIN)) tftp_request(packet, plen, listener, now); /* In single port mode, all packets come via port 69 and tftp_request() */ if (!option_bool(OPT_SINGLE_PORT)) for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next) if (poll_check(transfer->sockfd, POLLIN)) { union mysockaddr peer; socklen_t addr_len = sizeof(union mysockaddr); ssize_t len; if ((len = recvfrom(transfer->sockfd, packet, plen, 0, &peer.sa, &addr_len)) > 0) { #ifdef HAVE_DUMPFILE dump_packet_udp(DUMP_TFTP, (void *)packet, len, (union mysockaddr *)&peer, NULL, transfer->sockfd); #endif if (sockaddr_isequal(&peer, &transfer->peer)) handle_tftp(packet, now, transfer, len); else { /* Wrong source address. See rfc1350 para 4. */ prettyprint_addr(&peer, daemon->addrbuff); len = tftp_err(ERR_TID, packet, _("ignoring packet from %s (TID mismatch)"), daemon->addrbuff, NULL); while(retry_send(sendto(transfer->sockfd, packet, len, 0, &peer.sa, sa_len(&peer)))); #ifdef HAVE_DUMPFILE dump_packet_udp(DUMP_TFTP, (void *)packet, len, NULL, (union mysockaddr *)&peer, transfer->sockfd); #endif } } } for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp) { int endcon = 0, error = 0, timeout = 0; tmp = transfer->next; /* ->start set to zero in handle_tftp() when we recv an error packet. */ if (transfer->start == 0) endcon = error = 1; else if (difftime(now, transfer->start) > TFTP_TRANSFER_TIME) { endcon = 1; /* don't complain about timeout when we're awaiting the last ACK, some clients never send it */ if (get_block(transfer) > 0) error = timeout = 1; } else if (difftime(now, transfer->retransmit) >= 0.0) { /* Do transmission or re-transmission. When we get an ACK, the call to handle_tftp() bumps transfer->lastack and trips the retransmit timer so that we send the next block(s) here. */ ssize_t len; unsigned int i, winsize; transfer->retransmit += transfer->timeout + (1<<(transfer->backoff/2)); transfer->backoff++; transfer->block = transfer->lastack; /* send a window'a worth of blocks unless we're retransmitting OACK */ winsize = transfer->block ? transfer->windowsize : 1; for (i = 0; i < winsize; i++, transfer->block++) { if ((len = get_block(transfer)) == 0) { if (i == 0) endcon = 1; /* got last ACK */ break; } if (len == -1) { len = tftp_err_oops(daemon->packet, transfer->file->filename); endcon = error = 1; } send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), daemon->packet, len, &transfer->peer, &transfer->source, transfer->if_index); #ifdef HAVE_DUMPFILE dump_packet_udp(DUMP_TFTP, (void *)daemon->packet, len, NULL, (union mysockaddr *)&transfer->peer, transfer->sockfd); #endif } /* prefetch the block we'll probably need when we get an ACK. */ if (!endcon) get_block(transfer); } if (endcon) { strcpy(daemon->namebuff, transfer->file->filename); sanitise(daemon->namebuff); (void)prettyprint_addr(&transfer->peer, daemon->addrbuff); if (timeout) my_syslog(MS_TFTP | LOG_ERR, _("timeout sending %s to %s"), daemon->namebuff, daemon->addrbuff); else if (error) my_syslog(MS_TFTP | LOG_ERR, _("failed sending %s to %s"), daemon->namebuff, daemon->addrbuff); else my_syslog(MS_TFTP | LOG_INFO, _("sent %s to %s"), daemon->namebuff, daemon->addrbuff); /* unlink */ *up = tmp; if (error) free_transfer(transfer); else { /* put on queue to be sent to script and deleted */ transfer->next = daemon->tftp_done_trans; daemon->tftp_done_trans = transfer; } } else up = &transfer->next; } } static void handle_tftp(char *packet, time_t now, struct tftp_transfer *transfer, ssize_t len) { struct ack { unsigned short op, block; } *mess = (struct ack *)packet; if (len >= (ssize_t)sizeof(struct ack)) { if (ntohs(mess->op) == OP_ACK) { /* Handle 16-bit block number wrap-around. */ u16 new = ntohs(mess->block); u32 block; /* If the last ack received was in the top quarter of a 64k block and this one is in the bottom quarter, assume it has wrapped. Since this is UDP and an old packet can in theory wander in we may also need to drop back to a previous segment. Such an ACK is ignored below; here we're just getting the most likely 32 bit value from the 16 bits that we have. */ if (new <= 0x4000 && transfer->ackprev >= 0xc000) transfer->block_hi++; else if (new >= 0xc000 && transfer->ackprev <= 0x4000 && transfer->block_hi != 0) transfer->block_hi--; transfer->ackprev = new; block = (((u32)transfer->block_hi) << 16) + (u32)new; /* Ignore duplicate ACKs and ACKs for blocks we've not yet sent. */ if (block >= transfer->lastack && block <= transfer->block) { /* Got ack, move forward and ensure we take the (re)transmit path */ transfer->retransmit = transfer->start = now; transfer->backoff = 0; transfer->lastack = block + 1; /* We have no easy function from block no. to file offset when expanding line breaks in netascii mode, so we update the offset here as each block is acknowledged. This explains why the window size must be one for a netascii transfer; to avoid the block no. doing anything other than incrementing by one. */ if (transfer->netascii && block != 0) { transfer->offset += (off_t)transfer->blocksize - (off_t)transfer->expansion; transfer->lastcarrylf = transfer->carrylf; } } } else if (ntohs(mess->op) == OP_ERR) { char *p = packet + sizeof(struct ack); char *end = packet + len; char *err = next(&p, end); (void)prettyprint_addr(&transfer->peer, daemon->addrbuff); /* Sanitise error message */ if (!err) err = ""; else sanitise(err); my_syslog(MS_TFTP | LOG_ERR, _("error %d %s received from %s"), (int)ntohs(mess->block), err, daemon->addrbuff); /* Got err, ensure we take abort */ transfer->start = 0; } } } static void free_transfer(struct tftp_transfer *transfer) { if (!option_bool(OPT_SINGLE_PORT)) close(transfer->sockfd); if (transfer->file && (--transfer->file->refcount) == 0) { close(transfer->file->fd); free(transfer->file); } free(transfer); } static char *next(char **p, char *end) { char *n, *ret = *p; /* Look for end of string, without running off the end of the packet. */ for (n = *p; n < end && *n != 0; n++); /* ran off the end or zero length string - failed */ if (n == end || n == ret) return NULL; *p = n + 1; return ret; } /* If we don't do anything, don't write the the input/ouptut buffer. This allows us to pass in safe read-only strings constants. */ static void sanitise(char *buf) { unsigned char *q, *r; for (q = r = (unsigned char *)buf; *r; r++) if (isprint((int)*r)) { if (q != r) *q = *r; q++; } if (q != r) *q = 0; } #define MAXMESSAGE 500 /* limit to make packet < 512 bytes and definitely smaller than buffer */ static ssize_t tftp_err(int err, char *packet, char *message, char *file, char *arg2) { struct errmess { unsigned short op, err; char message[]; } *mess = (struct errmess *)packet; ssize_t len, ret = 4; if (file) sanitise(file); mess->op = htons(OP_ERR); mess->err = htons(err); len = snprintf(mess->message, MAXMESSAGE, message, file, arg2); ret += (len < MAXMESSAGE) ? len + 1 : MAXMESSAGE; /* include terminating zero */ if (err != ERR_FNF || !option_bool(OPT_QUIET_TFTP)) my_syslog(MS_TFTP | LOG_ERR, "%s", mess->message); return ret; } static ssize_t tftp_err_oops(char *packet, const char *file) { /* May have >1 refs to file, so potentially mangle a copy of the name */ if (file != daemon->namebuff) strcpy(daemon->namebuff, file); return tftp_err(ERR_NOTDEF, packet, _("cannot read %s: %s"), daemon->namebuff, strerror(errno)); } /* return -1 for error, zero for done. */ static ssize_t get_block(struct tftp_transfer *transfer) { static off_t saved_offset = 0; static ssize_t saved_len = 0; if (transfer->block == 0) { /* send OACK */ char *p; struct oackmess { unsigned short op; char data[]; } *mess = (struct oackmess *)daemon->packet; /* we overwrote the buffer... */ daemon->srv_save = NULL; memset(daemon->packet, 0, daemon->packet_buff_sz); p = mess->data; mess->op = htons(OP_OACK); if (transfer->opt_blocksize) { p += (sprintf(p, "blksize") + 1); p += (sprintf(p, "%u", transfer->blocksize) + 1); } if (transfer->opt_transize) { p += (sprintf(p,"tsize") + 1); p += (sprintf(p, "%u", (unsigned int)transfer->file->size) + 1); } if (transfer->opt_timeout) { p += (sprintf(p,"timeout") + 1); p += (sprintf(p, "%u", transfer->timeout) + 1); } if (transfer->opt_windowsize) { p += (sprintf(p,"windowsize") + 1); p += (sprintf(p, "%u", (unsigned int)transfer->windowsize) + 1); } return p - daemon->packet; } else { /* send data packet */ struct datamess { unsigned short op, block; unsigned char data[]; } *mess = (struct datamess *)daemon->packet; size_t size; if (!transfer->netascii) transfer->offset = (off_t)(transfer->block - 1) * (off_t)transfer->blocksize; if (transfer->offset > transfer->file->size) return 0; /* finished */ /* We may have a prefetched block already in the buffer. */ if (daemon->srv_save == transfer && saved_offset == transfer->offset) return saved_len; /* we overwrote the buffer... */ daemon->srv_save = NULL; if ((size = transfer->file->size - transfer->offset) > (size_t)transfer->blocksize) size = (size_t)transfer->blocksize; mess->op = htons(OP_DATA); mess->block = htons((unsigned short)(transfer->block)); if (size != 0) { if (transfer->file->posn != transfer->offset && lseek(transfer->file->fd, transfer->offset, SEEK_SET) == (off_t)-1) return -1; if (!read_write(transfer->file->fd, mess->data, size, RW_READ)) return -1; transfer->file->posn = transfer->offset + size; } /* Map '\n' to CR-LF in netascii mode */ if (transfer->netascii) { size_t i; /* Map '\n' to CR-LF in netascii mode */ transfer->expansion = transfer->carrylf = 0; for (i = 0; i < size; i++) if (mess->data[i] == '\n' && (i != 0 || !transfer->lastcarrylf)) { transfer->expansion++; if (size != transfer->blocksize) size++; /* room in this block */ else if (i == size - 1) transfer->carrylf = 1; /* don't expand LF again if it moves to the next block */ /* make space and insert CR */ memmove(&mess->data[i+1], &mess->data[i], size - (i + 1)); mess->data[i] = '\r'; i++; } } daemon->srv_save = transfer; saved_offset = transfer->offset; saved_len = size + 4; return saved_len; } } int do_tftp_script_run(void) { struct tftp_transfer *transfer; if ((transfer = daemon->tftp_done_trans)) { daemon->tftp_done_trans = transfer->next; #ifdef HAVE_SCRIPT queue_tftp(transfer->file->size, transfer->file->filename, &transfer->peer); #endif free_transfer(transfer); return 1; } return 0; } #endif