From 60704f5e2e1a92010e8e84ab4efc0be68c22458f Mon Sep 17 00:00:00 2001 From: Floris Bos Date: Sun, 9 Apr 2017 22:22:49 +0100 Subject: [PATCH] Add support for unique TFTP root per MAC. It is currently only possible to let the TFTP server serve a different folder depending on the client's IP address. However it isn't always possible to predict what the client's IP address will be, especially in situations in which we are not responsible for handing them out (e.g. proxy dhcp setups). Extend the current --tftp-unique-root parameter to support having a separate folder per MAC address instead. --- CHANGELOG | 3 +++ man/dnsmasq.8 | 16 ++++++++++------ src/dnsmasq.h | 5 +++-- src/option.c | 13 +++++++++++-- src/tftp.c | 34 +++++++++++++++++++++++++++++++++- 5 files changed, 60 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7fa39b8..3ad5e47 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -79,6 +79,9 @@ version 2.77 Make --bogus-priv apply to IPv6, for the prefixes specified in RFC6303. Thanks to Kevin Darbyshire-Bryant for work on this. + + Allow use of MAC addresses with --tftp-unique-root. Thanks + to Floris Bos for the patch. version 2.76 diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 2e5ef21..f7163e5 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -1808,12 +1808,16 @@ directory is only used for TFTP requests via that interface. .B --tftp-no-fail Do not abort startup if specified tftp root directories are inaccessible. .TP -.B --tftp-unique-root -Add the IP address of the TFTP client as a path component on the end -of the TFTP-root (in standard dotted-quad format). Only valid if a -tftp-root is set and the directory exists. For instance, if tftp-root is "/tftp" and client -1.2.3.4 requests file "myfile" then the effective path will be -"/tftp/1.2.3.4/myfile" if /tftp/1.2.3.4 exists or /tftp/myfile otherwise. +.B --tftp-unique-root[=ip|mac] +Add the IP or hardware address of the TFTP client as a path component on the end +of the TFTP-root. Only valid if a tftp-root is set and the directory exists. +Defaults to adding IP address (in standard dotted-quad format). +For instance, if tftp-root is "/tftp" and client 1.2.3.4 requests file "myfile" +then the effective path will be "/tftp/1.2.3.4/myfile" if /tftp/1.2.3.4 exists or /tftp/myfile otherwise. +When "=mac" is specified it will append the MAC address instead, using lowercase zero padded digits +separated by dashes, e.g.: 01-02-03-04-aa-bb +Note that resolving MAC addresses is only possible if the client is in the local network or obtained +a DHCP lease from us. .TP .B --tftp-secure Enable TFTP secure mode: without this, any file which is readable by diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 6b44e53..1fc3bec 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -211,7 +211,7 @@ struct event_desc { #define OPT_TFTP_SECURE 26 #define OPT_TFTP_NOBLOCK 27 #define OPT_LOG_OPTS 28 -#define OPT_TFTP_APREF 29 +#define OPT_TFTP_APREF_IP 29 #define OPT_NO_OVERRIDE 30 #define OPT_NO_REBIND 31 #define OPT_ADD_MAC 32 @@ -238,7 +238,8 @@ struct event_desc { #define OPT_SCRIPT_ARP 53 #define OPT_MAC_B64 54 #define OPT_MAC_HEX 55 -#define OPT_LAST 56 +#define OPT_TFTP_APREF_MAC 56 +#define OPT_LAST 57 /* extra flags for my_syslog, we use a couple of facilities since they are known not to occupy the same bits as priorities, no matter how syslog.h is set up. */ diff --git a/src/option.c b/src/option.c index 0c38db3..a3ddec1 100644 --- a/src/option.c +++ b/src/option.c @@ -242,7 +242,7 @@ static const struct myoption opts[] = { "enable-tftp", 2, 0, LOPT_TFTP }, { "tftp-secure", 0, 0, LOPT_SECURE }, { "tftp-no-fail", 0, 0, LOPT_TFTP_NO_FAIL }, - { "tftp-unique-root", 0, 0, LOPT_APREF }, + { "tftp-unique-root", 2, 0, LOPT_APREF }, { "tftp-root", 1, 0, LOPT_PREFIX }, { "tftp-max", 1, 0, LOPT_TFTP_MAX }, { "tftp-mtu", 1, 0, LOPT_TFTP_MTU }, @@ -430,7 +430,7 @@ static struct { { LOPT_OVERRIDE, OPT_NO_OVERRIDE, NULL, gettext_noop("Do NOT reuse filename and server fields for extra DHCP options."), NULL }, { LOPT_TFTP, ARG_DUP, "[=[,]]", gettext_noop("Enable integrated read-only TFTP server."), NULL }, { LOPT_PREFIX, ARG_DUP, "[,]", gettext_noop("Export files by TFTP only from the specified subtree."), NULL }, - { LOPT_APREF, OPT_TFTP_APREF, NULL, gettext_noop("Add client IP address to tftp-root."), NULL }, + { LOPT_APREF, ARG_DUP, "[=ip|mac]", gettext_noop("Add client IP or hardware address to tftp-root."), NULL }, { LOPT_SECURE, OPT_TFTP_SECURE, NULL, gettext_noop("Allow access only to files owned by the user running dnsmasq."), NULL }, { LOPT_TFTP_NO_FAIL, OPT_TFTP_NO_FAIL, NULL, gettext_noop("Do not terminate the service if TFTP directories are inaccessible."), NULL }, { LOPT_TFTP_MAX, ARG_ONE, "", gettext_noop("Maximum number of concurrent TFTP transfers (defaults to %s)."), "#" }, @@ -2717,6 +2717,15 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } break; + + case LOPT_APREF: /* --tftp-unique-root */ + if (!arg || strcasecmp(arg, "ip") == 0) + set_option_bool(OPT_TFTP_APREF_IP); + else if (strcasecmp(arg, "mac") == 0) + set_option_bool(OPT_TFTP_APREF_MAC); + else + ret_err(gen_err); + break; #endif case LOPT_BRIDGE: /* --bridge-interface */ diff --git a/src/tftp.c b/src/tftp.c index f8ce9d2..be0d474 100644 --- a/src/tftp.c +++ b/src/tftp.c @@ -382,7 +382,7 @@ void tftp_request(struct listener *listen, time_t now) if (prefix[strlen(prefix)-1] != '/') strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff)); - if (option_bool(OPT_TFTP_APREF)) + if (option_bool(OPT_TFTP_APREF_IP)) { size_t oldlen = strlen(daemon->namebuff); struct stat statbuf; @@ -394,6 +394,38 @@ void tftp_request(struct listener *listen, time_t now) 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] == '/')