967 lines
26 KiB
C
967 lines
26 KiB
C
/* dnsmasq is Copyright (c) 2000-2024 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "dnsmasq.h"
|
|
#include "../log.h"
|
|
|
|
#ifdef HAVE_SCRIPT
|
|
|
|
/* This file has code to fork a helper process which receives data via a pipe
|
|
shared with the main process and which is responsible for calling a script when
|
|
DHCP leases change.
|
|
|
|
The helper process is forked before the main process drops root, so it retains root
|
|
privs to pass on to the script. For this reason it tries to be paranoid about
|
|
data received from the main process, in case that has been compromised. We don't
|
|
want the helper to give an attacker root. In particular, the script to be run is
|
|
not settable via the pipe, once the fork has taken place it is not alterable by the
|
|
main process.
|
|
*/
|
|
|
|
static void my_setenv(const char *name, const char *value, int *error);
|
|
static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, char *env, int *err);
|
|
|
|
#ifdef HAVE_LUASCRIPT
|
|
#define LUA_COMPAT_ALL
|
|
#include <lua.h>
|
|
#include <lualib.h>
|
|
#include <lauxlib.h>
|
|
|
|
#ifndef lua_open
|
|
#define lua_open() luaL_newstate()
|
|
#endif
|
|
|
|
lua_State *lua;
|
|
|
|
static unsigned char *grab_extradata_lua(unsigned char *buf, unsigned char *end, char *field);
|
|
#endif
|
|
|
|
|
|
struct script_data
|
|
{
|
|
int flags;
|
|
int action, hwaddr_len, hwaddr_type;
|
|
int clid_len, hostname_len, ed_len;
|
|
struct in_addr addr, giaddr;
|
|
unsigned int remaining_time;
|
|
#ifdef HAVE_BROKEN_RTC
|
|
unsigned int length;
|
|
#else
|
|
time_t expires;
|
|
#endif
|
|
#ifdef HAVE_TFTP
|
|
off_t file_len;
|
|
#endif
|
|
struct in6_addr addr6;
|
|
#ifdef HAVE_DHCP6
|
|
int vendorclass_count;
|
|
unsigned int iaid;
|
|
#endif
|
|
unsigned char hwaddr[DHCP_CHADDR_MAX];
|
|
char interface[IF_NAMESIZE];
|
|
};
|
|
|
|
static struct script_data *buf = NULL;
|
|
static size_t bytes_in_buf = 0, buf_size = 0;
|
|
|
|
int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
|
|
{
|
|
pid_t pid;
|
|
int i, pipefd[2];
|
|
struct sigaction sigact;
|
|
unsigned char *alloc_buff = NULL;
|
|
|
|
/* create the pipe through which the main program sends us commands,
|
|
then fork our process. */
|
|
if (pipe(pipefd) == -1 || !fix_fd(pipefd[1]) || (pid = fork()) == -1)
|
|
{
|
|
send_event(err_fd, EVENT_PIPE_ERR, errno, NULL);
|
|
_exit(0);
|
|
}
|
|
|
|
if (pid != 0)
|
|
{
|
|
close(pipefd[0]); /* close reader side */
|
|
return pipefd[1];
|
|
}
|
|
|
|
/**** Pi-hole modification ****/
|
|
logg("Started script helper");
|
|
/******************************/
|
|
|
|
/* ignore SIGTERM and SIGINT, so that we can clean up when the main process gets hit
|
|
and SIGALRM so that we can use sleep() */
|
|
sigact.sa_handler = SIG_IGN;
|
|
sigact.sa_flags = 0;
|
|
sigemptyset(&sigact.sa_mask);
|
|
sigaction(SIGTERM, &sigact, NULL);
|
|
sigaction(SIGALRM, &sigact, NULL);
|
|
sigaction(SIGINT, &sigact, NULL);
|
|
|
|
if (!option_bool(OPT_DEBUG) && uid != 0)
|
|
{
|
|
gid_t dummy;
|
|
if (setgroups(0, &dummy) == -1 ||
|
|
setgid(gid) == -1 ||
|
|
setuid(uid) == -1)
|
|
{
|
|
if (option_bool(OPT_NO_FORK))
|
|
/* send error to daemon process if no-fork */
|
|
send_event(event_fd, EVENT_USER_ERR, errno, daemon->scriptuser);
|
|
else
|
|
{
|
|
/* kill daemon */
|
|
send_event(event_fd, EVENT_DIE, 0, NULL);
|
|
/* return error */
|
|
send_event(err_fd, EVENT_USER_ERR, errno, daemon->scriptuser);
|
|
}
|
|
_exit(0);
|
|
}
|
|
}
|
|
|
|
/* close all the sockets etc, we don't need them here.
|
|
Don't close err_fd, in case the lua-init fails.
|
|
Note that we have to do this before lua init
|
|
so we don't close any lua fds. */
|
|
close_fds(max_fd, pipefd[0], event_fd, err_fd);
|
|
|
|
#ifdef HAVE_LUASCRIPT
|
|
if (daemon->luascript)
|
|
{
|
|
const char *lua_err = NULL;
|
|
lua = lua_open();
|
|
luaL_openlibs(lua);
|
|
|
|
/* get Lua to load our script file */
|
|
if (luaL_dofile(lua, daemon->luascript) != 0)
|
|
lua_err = lua_tostring(lua, -1);
|
|
else
|
|
{
|
|
lua_getglobal(lua, "lease");
|
|
if (lua_type(lua, -1) != LUA_TFUNCTION)
|
|
lua_err = _("lease() function missing in Lua script");
|
|
}
|
|
|
|
if (lua_err)
|
|
{
|
|
if (option_bool(OPT_NO_FORK) || option_bool(OPT_DEBUG))
|
|
/* send error to daemon process if no-fork */
|
|
send_event(event_fd, EVENT_LUA_ERR, 0, (char *)lua_err);
|
|
else
|
|
{
|
|
/* kill daemon */
|
|
send_event(event_fd, EVENT_DIE, 0, NULL);
|
|
/* return error */
|
|
send_event(err_fd, EVENT_LUA_ERR, 0, (char *)lua_err);
|
|
}
|
|
_exit(0);
|
|
}
|
|
|
|
lua_pop(lua, 1); /* remove nil from stack */
|
|
lua_getglobal(lua, "init");
|
|
if (lua_type(lua, -1) == LUA_TFUNCTION)
|
|
lua_call(lua, 0, 0);
|
|
else
|
|
lua_pop(lua, 1); /* remove nil from stack */
|
|
}
|
|
#endif
|
|
|
|
/* All init done, close our copy of the error pipe, so that main process can return */
|
|
if (err_fd != -1)
|
|
close(err_fd);
|
|
|
|
/* loop here */
|
|
while(1)
|
|
{
|
|
struct script_data data;
|
|
char *p, *action_str, *hostname = NULL, *domain = NULL;
|
|
unsigned char *buf = (unsigned char *)daemon->namebuff;
|
|
unsigned char *end, *extradata;
|
|
int is6, err = 0;
|
|
int pipeout[2];
|
|
|
|
/* Free rarely-allocated memory from previous iteration. */
|
|
if (alloc_buff)
|
|
{
|
|
free(alloc_buff);
|
|
alloc_buff = NULL;
|
|
}
|
|
|
|
/* we read zero bytes when pipe closed: this is our signal to exit */
|
|
if (!read_write(pipefd[0], (unsigned char *)&data, sizeof(data), 1))
|
|
{
|
|
#ifdef HAVE_LUASCRIPT
|
|
if (daemon->luascript)
|
|
{
|
|
lua_getglobal(lua, "shutdown");
|
|
if (lua_type(lua, -1) == LUA_TFUNCTION)
|
|
lua_call(lua, 0, 0);
|
|
}
|
|
#endif
|
|
_exit(0);
|
|
}
|
|
|
|
is6 = !!(data.flags & (LEASE_TA | LEASE_NA));
|
|
|
|
if (data.action == ACTION_DEL)
|
|
action_str = "del";
|
|
else if (data.action == ACTION_ADD)
|
|
action_str = "add";
|
|
else if (data.action == ACTION_OLD || data.action == ACTION_OLD_HOSTNAME)
|
|
action_str = "old";
|
|
else if (data.action == ACTION_TFTP)
|
|
{
|
|
action_str = "tftp";
|
|
is6 = (data.flags != AF_INET);
|
|
}
|
|
else if (data.action == ACTION_ARP)
|
|
{
|
|
action_str = "arp-add";
|
|
is6 = (data.flags != AF_INET);
|
|
}
|
|
else if (data.action == ACTION_ARP_DEL)
|
|
{
|
|
action_str = "arp-del";
|
|
is6 = (data.flags != AF_INET);
|
|
data.action = ACTION_ARP;
|
|
}
|
|
else if (data.action == ACTION_RELAY_SNOOP)
|
|
{
|
|
is6 = 1;
|
|
action_str = "relay-snoop";
|
|
}
|
|
else
|
|
continue;
|
|
|
|
/************************** Pi-hole modification **************************/
|
|
FTL_log_helper(1, action_str);
|
|
/**************************************************************************/
|
|
|
|
/* stringify MAC into dhcp_buff */
|
|
p = daemon->dhcp_buff;
|
|
if (data.hwaddr_type != ARPHRD_ETHER || data.hwaddr_len == 0)
|
|
p += sprintf(p, "%.2x-", data.hwaddr_type);
|
|
for (i = 0; (i < data.hwaddr_len) && (i < DHCP_CHADDR_MAX); i++)
|
|
{
|
|
p += sprintf(p, "%.2x", data.hwaddr[i]);
|
|
if (i != data.hwaddr_len - 1)
|
|
p += sprintf(p, ":");
|
|
}
|
|
|
|
/* supplied data may just exceed normal buffer (unlikely) */
|
|
if ((data.hostname_len + data.ed_len + data.clid_len) > MAXDNAME &&
|
|
!(alloc_buff = buf = malloc(data.hostname_len + data.ed_len + data.clid_len)))
|
|
continue;
|
|
|
|
if (!read_write(pipefd[0], buf,
|
|
data.hostname_len + data.ed_len + data.clid_len, 1))
|
|
continue;
|
|
|
|
/* CLID into packet */
|
|
for (p = daemon->packet, i = 0; i < data.clid_len; i++)
|
|
{
|
|
p += sprintf(p, "%.2x", buf[i]);
|
|
if (i != data.clid_len - 1)
|
|
p += sprintf(p, ":");
|
|
}
|
|
|
|
#ifdef HAVE_DHCP6
|
|
if (is6)
|
|
{
|
|
/* or IAID and server DUID for IPv6 */
|
|
sprintf(daemon->dhcp_buff3, "%s%u", data.flags & LEASE_TA ? "T" : "", data.iaid);
|
|
for (p = daemon->dhcp_packet.iov_base, i = 0; i < daemon->duid_len; i++)
|
|
{
|
|
p += sprintf(p, "%.2x", daemon->duid[i]);
|
|
if (i != daemon->duid_len - 1)
|
|
p += sprintf(p, ":");
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
buf += data.clid_len;
|
|
|
|
if (data.hostname_len != 0)
|
|
{
|
|
char *dot;
|
|
hostname = (char *)buf;
|
|
hostname[data.hostname_len - 1] = 0;
|
|
if (data.action != ACTION_TFTP && data.action != ACTION_RELAY_SNOOP)
|
|
{
|
|
if (!legal_hostname(hostname))
|
|
hostname = NULL;
|
|
else if ((dot = strchr(hostname, '.')))
|
|
{
|
|
domain = dot+1;
|
|
*dot = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
extradata = buf + data.hostname_len;
|
|
|
|
if (!is6)
|
|
inet_ntop(AF_INET, &data.addr, daemon->addrbuff, ADDRSTRLEN);
|
|
else
|
|
inet_ntop(AF_INET6, &data.addr6, daemon->addrbuff, ADDRSTRLEN);
|
|
|
|
#ifdef HAVE_TFTP
|
|
/* file length */
|
|
if (data.action == ACTION_TFTP)
|
|
sprintf(is6 ? daemon->packet : daemon->dhcp_buff, "%lu", (unsigned long)data.file_len);
|
|
#endif
|
|
|
|
#ifdef HAVE_LUASCRIPT
|
|
if (daemon->luascript)
|
|
{
|
|
if (data.action == ACTION_TFTP)
|
|
{
|
|
lua_getglobal(lua, "tftp");
|
|
if (lua_type(lua, -1) != LUA_TFUNCTION)
|
|
lua_pop(lua, 1); /* tftp function optional */
|
|
else
|
|
{
|
|
lua_pushstring(lua, action_str); /* arg1 - action */
|
|
lua_newtable(lua); /* arg2 - data table */
|
|
lua_pushstring(lua, daemon->addrbuff);
|
|
lua_setfield(lua, -2, "destination_address");
|
|
lua_pushstring(lua, hostname);
|
|
lua_setfield(lua, -2, "file_name");
|
|
lua_pushstring(lua, is6 ? daemon->packet : daemon->dhcp_buff);
|
|
lua_setfield(lua, -2, "file_size");
|
|
lua_call(lua, 2, 0); /* pass 2 values, expect 0 */
|
|
}
|
|
}
|
|
else if (data.action == ACTION_RELAY_SNOOP)
|
|
{
|
|
lua_getglobal(lua, "snoop");
|
|
if (lua_type(lua, -1) != LUA_TFUNCTION)
|
|
lua_pop(lua, 1); /* tftp function optional */
|
|
else
|
|
{
|
|
lua_pushstring(lua, action_str); /* arg1 - action */
|
|
lua_newtable(lua); /* arg2 - data table */
|
|
lua_pushstring(lua, daemon->addrbuff);
|
|
lua_setfield(lua, -2, "client_address");
|
|
lua_pushstring(lua, hostname);
|
|
lua_setfield(lua, -2, "prefix");
|
|
lua_pushstring(lua, data.interface);
|
|
lua_setfield(lua, -2, "client_interface");
|
|
lua_call(lua, 2, 0); /* pass 2 values, expect 0 */
|
|
}
|
|
}
|
|
else if (data.action == ACTION_ARP)
|
|
{
|
|
lua_getglobal(lua, "arp");
|
|
if (lua_type(lua, -1) != LUA_TFUNCTION)
|
|
lua_pop(lua, 1); /* arp function optional */
|
|
else
|
|
{
|
|
lua_pushstring(lua, action_str); /* arg1 - action */
|
|
lua_newtable(lua); /* arg2 - data table */
|
|
lua_pushstring(lua, daemon->addrbuff);
|
|
lua_setfield(lua, -2, "client_address");
|
|
lua_pushstring(lua, daemon->dhcp_buff);
|
|
lua_setfield(lua, -2, "mac_address");
|
|
lua_call(lua, 2, 0); /* pass 2 values, expect 0 */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lua_getglobal(lua, "lease"); /* function to call */
|
|
lua_pushstring(lua, action_str); /* arg1 - action */
|
|
lua_newtable(lua); /* arg2 - data table */
|
|
|
|
if (is6)
|
|
{
|
|
lua_pushstring(lua, daemon->packet);
|
|
lua_setfield(lua, -2, "client_duid");
|
|
lua_pushstring(lua, daemon->dhcp_packet.iov_base);
|
|
lua_setfield(lua, -2, "server_duid");
|
|
lua_pushstring(lua, daemon->dhcp_buff3);
|
|
lua_setfield(lua, -2, "iaid");
|
|
}
|
|
|
|
if (!is6 && data.clid_len != 0)
|
|
{
|
|
lua_pushstring(lua, daemon->packet);
|
|
lua_setfield(lua, -2, "client_id");
|
|
}
|
|
|
|
if (strlen(data.interface) != 0)
|
|
{
|
|
lua_pushstring(lua, data.interface);
|
|
lua_setfield(lua, -2, "interface");
|
|
}
|
|
|
|
#ifdef HAVE_BROKEN_RTC
|
|
lua_pushnumber(lua, data.length);
|
|
lua_setfield(lua, -2, "lease_length");
|
|
#else
|
|
lua_pushnumber(lua, data.expires);
|
|
lua_setfield(lua, -2, "lease_expires");
|
|
#endif
|
|
|
|
if (hostname)
|
|
{
|
|
lua_pushstring(lua, hostname);
|
|
lua_setfield(lua, -2, "hostname");
|
|
}
|
|
|
|
if (domain)
|
|
{
|
|
lua_pushstring(lua, domain);
|
|
lua_setfield(lua, -2, "domain");
|
|
}
|
|
|
|
end = extradata + data.ed_len;
|
|
buf = extradata;
|
|
|
|
lua_pushnumber(lua, data.ed_len == 0 ? 1 : 0);
|
|
lua_setfield(lua, -2, "data_missing");
|
|
|
|
if (!is6)
|
|
buf = grab_extradata_lua(buf, end, "vendor_class");
|
|
#ifdef HAVE_DHCP6
|
|
else if (data.vendorclass_count != 0)
|
|
{
|
|
sprintf(daemon->dhcp_buff2, "vendor_class_id");
|
|
buf = grab_extradata_lua(buf, end, daemon->dhcp_buff2);
|
|
for (i = 0; i < data.vendorclass_count - 1; i++)
|
|
{
|
|
sprintf(daemon->dhcp_buff2, "vendor_class%i", i);
|
|
buf = grab_extradata_lua(buf, end, daemon->dhcp_buff2);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
buf = grab_extradata_lua(buf, end, "supplied_hostname");
|
|
|
|
if (!is6)
|
|
{
|
|
buf = grab_extradata_lua(buf, end, "cpewan_oui");
|
|
buf = grab_extradata_lua(buf, end, "cpewan_serial");
|
|
buf = grab_extradata_lua(buf, end, "cpewan_class");
|
|
buf = grab_extradata_lua(buf, end, "circuit_id");
|
|
buf = grab_extradata_lua(buf, end, "subscriber_id");
|
|
buf = grab_extradata_lua(buf, end, "remote_id");
|
|
}
|
|
|
|
buf = grab_extradata_lua(buf, end, "requested_options");
|
|
buf = grab_extradata_lua(buf, end, "mud_url");
|
|
buf = grab_extradata_lua(buf, end, "tags");
|
|
|
|
if (is6)
|
|
buf = grab_extradata_lua(buf, end, "relay_address");
|
|
else if (data.giaddr.s_addr != 0)
|
|
{
|
|
inet_ntop(AF_INET, &data.giaddr, daemon->dhcp_buff2, ADDRSTRLEN);
|
|
lua_pushstring(lua, daemon->dhcp_buff2);
|
|
lua_setfield(lua, -2, "relay_address");
|
|
}
|
|
|
|
for (i = 0; buf; i++)
|
|
{
|
|
sprintf(daemon->dhcp_buff2, "user_class%i", i);
|
|
buf = grab_extradata_lua(buf, end, daemon->dhcp_buff2);
|
|
}
|
|
|
|
if (data.action != ACTION_DEL && data.remaining_time != 0)
|
|
{
|
|
lua_pushnumber(lua, data.remaining_time);
|
|
lua_setfield(lua, -2, "time_remaining");
|
|
}
|
|
|
|
if (data.action == ACTION_OLD_HOSTNAME && hostname)
|
|
{
|
|
lua_pushstring(lua, hostname);
|
|
lua_setfield(lua, -2, "old_hostname");
|
|
}
|
|
|
|
if (!is6 || data.hwaddr_len != 0)
|
|
{
|
|
lua_pushstring(lua, daemon->dhcp_buff);
|
|
lua_setfield(lua, -2, "mac_address");
|
|
}
|
|
|
|
lua_pushstring(lua, daemon->addrbuff);
|
|
lua_setfield(lua, -2, "ip_address");
|
|
|
|
lua_call(lua, 2, 0); /* pass 2 values, expect 0 */
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* no script, just lua */
|
|
if (!daemon->lease_change_command)
|
|
continue;
|
|
|
|
/* Pipe to capture stdout and stderr from script */
|
|
if (!option_bool(OPT_DEBUG) && pipe(pipeout) == -1)
|
|
continue;
|
|
|
|
/* possible fork errors are all temporary resource problems */
|
|
while ((pid = fork()) == -1 && (errno == EAGAIN || errno == ENOMEM))
|
|
sleep(2);
|
|
|
|
if (pid == -1)
|
|
{
|
|
if (!option_bool(OPT_DEBUG))
|
|
{
|
|
close(pipeout[0]);
|
|
close(pipeout[1]);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* wait for child to complete */
|
|
if (pid != 0)
|
|
{
|
|
if (!option_bool(OPT_DEBUG))
|
|
{
|
|
FILE *fp;
|
|
|
|
close(pipeout[1]);
|
|
|
|
/* Read lines sent to stdout/err by the script and pass them back to be logged */
|
|
if (!(fp = fdopen(pipeout[0], "r")))
|
|
close(pipeout[0]);
|
|
else
|
|
{
|
|
while (fgets(daemon->packet, daemon->packet_buff_sz, fp))
|
|
{
|
|
/* do not include new lines, log will append them */
|
|
size_t len = strlen(daemon->packet);
|
|
if (len > 0)
|
|
{
|
|
--len;
|
|
if (daemon->packet[len] == '\n')
|
|
daemon->packet[len] = 0;
|
|
}
|
|
send_event(event_fd, EVENT_SCRIPT_LOG, 0, daemon->packet);
|
|
}
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
/* reap our children's children, if necessary */
|
|
while (1)
|
|
{
|
|
int status;
|
|
pid_t rc = wait(&status);
|
|
|
|
if (rc == pid)
|
|
{
|
|
/* On error send event back to main process for logging */
|
|
if (WIFSIGNALED(status))
|
|
send_event(event_fd, EVENT_KILLED, WTERMSIG(status), NULL);
|
|
else if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
|
|
send_event(event_fd, EVENT_EXITED, WEXITSTATUS(status), NULL);
|
|
break;
|
|
}
|
|
|
|
if (rc == -1 && errno != EINTR)
|
|
break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (!option_bool(OPT_DEBUG))
|
|
{
|
|
/* map stdout/stderr of script to pipeout */
|
|
close(pipeout[0]);
|
|
dup2(pipeout[1], STDOUT_FILENO);
|
|
dup2(pipeout[1], STDERR_FILENO);
|
|
close(pipeout[1]);
|
|
}
|
|
|
|
if (data.action != ACTION_TFTP && data.action != ACTION_ARP && data.action != ACTION_RELAY_SNOOP)
|
|
{
|
|
#ifdef HAVE_DHCP6
|
|
my_setenv("DNSMASQ_IAID", is6 ? daemon->dhcp_buff3 : NULL, &err);
|
|
my_setenv("DNSMASQ_SERVER_DUID", is6 ? daemon->dhcp_packet.iov_base : NULL, &err);
|
|
my_setenv("DNSMASQ_MAC", is6 && data.hwaddr_len != 0 ? daemon->dhcp_buff : NULL, &err);
|
|
#endif
|
|
|
|
my_setenv("DNSMASQ_CLIENT_ID", !is6 && data.clid_len != 0 ? daemon->packet : NULL, &err);
|
|
my_setenv("DNSMASQ_INTERFACE", strlen(data.interface) != 0 ? data.interface : NULL, &err);
|
|
|
|
#ifdef HAVE_BROKEN_RTC
|
|
sprintf(daemon->dhcp_buff2, "%u", data.length);
|
|
my_setenv("DNSMASQ_LEASE_LENGTH", daemon->dhcp_buff2, &err);
|
|
#else
|
|
sprintf(daemon->dhcp_buff2, "%lu", (unsigned long)data.expires);
|
|
my_setenv("DNSMASQ_LEASE_EXPIRES", daemon->dhcp_buff2, &err);
|
|
#endif
|
|
|
|
my_setenv("DNSMASQ_DOMAIN", domain, &err);
|
|
|
|
end = extradata + data.ed_len;
|
|
buf = extradata;
|
|
|
|
if (data.ed_len == 0)
|
|
my_setenv("DNSMASQ_DATA_MISSING", "1", &err);
|
|
|
|
if (!is6)
|
|
buf = grab_extradata(buf, end, "DNSMASQ_VENDOR_CLASS", &err);
|
|
#ifdef HAVE_DHCP6
|
|
else
|
|
{
|
|
if (data.vendorclass_count != 0)
|
|
{
|
|
buf = grab_extradata(buf, end, "DNSMASQ_VENDOR_CLASS_ID", &err);
|
|
for (i = 0; i < data.vendorclass_count - 1; i++)
|
|
{
|
|
sprintf(daemon->dhcp_buff2, "DNSMASQ_VENDOR_CLASS%i", i);
|
|
buf = grab_extradata(buf, end, daemon->dhcp_buff2, &err);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
buf = grab_extradata(buf, end, "DNSMASQ_SUPPLIED_HOSTNAME", &err);
|
|
|
|
if (!is6)
|
|
{
|
|
buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_OUI", &err);
|
|
buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_SERIAL", &err);
|
|
buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_CLASS", &err);
|
|
buf = grab_extradata(buf, end, "DNSMASQ_CIRCUIT_ID", &err);
|
|
buf = grab_extradata(buf, end, "DNSMASQ_SUBSCRIBER_ID", &err);
|
|
buf = grab_extradata(buf, end, "DNSMASQ_REMOTE_ID", &err);
|
|
}
|
|
|
|
buf = grab_extradata(buf, end, "DNSMASQ_REQUESTED_OPTIONS", &err);
|
|
buf = grab_extradata(buf, end, "DNSMASQ_MUD_URL", &err);
|
|
buf = grab_extradata(buf, end, "DNSMASQ_TAGS", &err);
|
|
|
|
if (is6)
|
|
buf = grab_extradata(buf, end, "DNSMASQ_RELAY_ADDRESS", &err);
|
|
else
|
|
{
|
|
const char *giaddr = NULL;
|
|
if (data.giaddr.s_addr != 0)
|
|
giaddr = inet_ntop(AF_INET, &data.giaddr, daemon->dhcp_buff2, ADDRSTRLEN);
|
|
my_setenv("DNSMASQ_RELAY_ADDRESS", giaddr, &err);
|
|
}
|
|
|
|
for (i = 0; buf; i++)
|
|
{
|
|
sprintf(daemon->dhcp_buff2, "DNSMASQ_USER_CLASS%i", i);
|
|
buf = grab_extradata(buf, end, daemon->dhcp_buff2, &err);
|
|
}
|
|
|
|
sprintf(daemon->dhcp_buff2, "%u", data.remaining_time);
|
|
my_setenv("DNSMASQ_TIME_REMAINING", data.action != ACTION_DEL && data.remaining_time != 0 ? daemon->dhcp_buff2 : NULL, &err);
|
|
|
|
my_setenv("DNSMASQ_OLD_HOSTNAME", data.action == ACTION_OLD_HOSTNAME ? hostname : NULL, &err);
|
|
if (data.action == ACTION_OLD_HOSTNAME)
|
|
hostname = NULL;
|
|
|
|
my_setenv("DNSMASQ_LOG_DHCP", option_bool(OPT_LOG_OPTS) ? "1" : NULL, &err);
|
|
}
|
|
|
|
/* we need to have the event_fd around if exec fails */
|
|
if ((i = fcntl(event_fd, F_GETFD)) != -1)
|
|
fcntl(event_fd, F_SETFD, i | FD_CLOEXEC);
|
|
close(pipefd[0]);
|
|
|
|
if (data.action == ACTION_RELAY_SNOOP)
|
|
strcpy(daemon->packet, data.interface);
|
|
|
|
/**************************** Pi-hole modification ****************************/
|
|
FTL_log_helper(5, daemon->lease_change_command, action_str,
|
|
(is6 && data.action != ACTION_ARP) ? daemon->packet : daemon->dhcp_buff,
|
|
daemon->addrbuff, hostname);
|
|
/******************************************************************************/
|
|
|
|
p = strrchr(daemon->lease_change_command, '/');
|
|
if (err == 0)
|
|
{
|
|
execl(daemon->lease_change_command,
|
|
p ? p+1 : daemon->lease_change_command, action_str,
|
|
(is6 && data.action != ACTION_ARP) ? daemon->packet : daemon->dhcp_buff,
|
|
daemon->addrbuff, hostname, (char*)NULL);
|
|
err = errno;
|
|
}
|
|
/* failed, send event so the main process logs the problem */
|
|
/**************************** Pi-hole modification ****************************/
|
|
FTL_log_helper(2, daemon->lease_change_command, strerror(err));
|
|
/******************************************************************************/
|
|
send_event(event_fd, EVENT_EXEC_ERR, err, NULL);
|
|
_exit(0);
|
|
}
|
|
}
|
|
|
|
static void my_setenv(const char *name, const char *value, int *error)
|
|
{
|
|
if (*error == 0)
|
|
{
|
|
if (!value)
|
|
unsetenv(name);
|
|
else if (setenv(name, value, 1) != 0)
|
|
*error = errno;
|
|
}
|
|
}
|
|
|
|
static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, char *env, int *err)
|
|
{
|
|
unsigned char *next = NULL;
|
|
char *val = NULL;
|
|
|
|
if (buf && (buf != end))
|
|
{
|
|
for (next = buf; ; next++)
|
|
if (next == end)
|
|
{
|
|
next = NULL;
|
|
break;
|
|
}
|
|
else if (*next == 0)
|
|
break;
|
|
|
|
if (next && (next != buf))
|
|
{
|
|
char *p;
|
|
/* No "=" in value */
|
|
if ((p = strchr((char *)buf, '=')))
|
|
*p = 0;
|
|
val = (char *)buf;
|
|
}
|
|
}
|
|
|
|
my_setenv(env, val, err);
|
|
|
|
return next ? next + 1 : NULL;
|
|
}
|
|
|
|
#ifdef HAVE_LUASCRIPT
|
|
static unsigned char *grab_extradata_lua(unsigned char *buf, unsigned char *end, char *field)
|
|
{
|
|
unsigned char *next;
|
|
|
|
if (!buf || (buf == end))
|
|
return NULL;
|
|
|
|
for (next = buf; *next != 0; next++)
|
|
if (next == end)
|
|
return NULL;
|
|
|
|
if (next != buf)
|
|
{
|
|
lua_pushstring(lua, (char *)buf);
|
|
lua_setfield(lua, -2, field);
|
|
}
|
|
|
|
return next + 1;
|
|
}
|
|
#endif
|
|
|
|
static void buff_alloc(size_t size)
|
|
{
|
|
if (size > buf_size)
|
|
{
|
|
struct script_data *new;
|
|
|
|
/* start with reasonable size, will almost never need extending. */
|
|
if (size < sizeof(struct script_data) + 200)
|
|
size = sizeof(struct script_data) + 200;
|
|
|
|
if (!(new = whine_malloc(size)))
|
|
return;
|
|
if (buf)
|
|
free(buf);
|
|
buf = new;
|
|
buf_size = size;
|
|
}
|
|
}
|
|
|
|
/* pack up lease data into a buffer */
|
|
void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t now)
|
|
{
|
|
unsigned char *p;
|
|
unsigned int hostname_len = 0, clid_len = 0, ed_len = 0;
|
|
int fd = daemon->dhcpfd;
|
|
#ifdef HAVE_DHCP6
|
|
if (!daemon->dhcp)
|
|
fd = daemon->dhcp6fd;
|
|
#endif
|
|
|
|
/* no script */
|
|
if (daemon->helperfd == -1)
|
|
return;
|
|
|
|
if (lease->extradata)
|
|
ed_len = lease->extradata_len;
|
|
if (lease->clid)
|
|
clid_len = lease->clid_len;
|
|
if (hostname)
|
|
hostname_len = strlen(hostname) + 1;
|
|
|
|
buff_alloc(sizeof(struct script_data) + clid_len + ed_len + hostname_len);
|
|
|
|
buf->action = action;
|
|
buf->flags = lease->flags;
|
|
#ifdef HAVE_DHCP6
|
|
buf->vendorclass_count = lease->vendorclass_count;
|
|
buf->addr6 = lease->addr6;
|
|
buf->iaid = lease->iaid;
|
|
#endif
|
|
buf->hwaddr_len = lease->hwaddr_len;
|
|
buf->hwaddr_type = lease->hwaddr_type;
|
|
buf->clid_len = clid_len;
|
|
buf->ed_len = ed_len;
|
|
buf->hostname_len = hostname_len;
|
|
buf->addr = lease->addr;
|
|
buf->giaddr = lease->giaddr;
|
|
memcpy(buf->hwaddr, lease->hwaddr, DHCP_CHADDR_MAX);
|
|
if (!indextoname(fd, lease->last_interface, buf->interface))
|
|
buf->interface[0] = 0;
|
|
|
|
#ifdef HAVE_BROKEN_RTC
|
|
buf->length = lease->length;
|
|
#else
|
|
buf->expires = lease->expires;
|
|
#endif
|
|
|
|
if (lease->expires != 0)
|
|
buf->remaining_time = (unsigned int)difftime(lease->expires, now);
|
|
else
|
|
buf->remaining_time = 0;
|
|
|
|
p = (unsigned char *)(buf+1);
|
|
if (clid_len != 0)
|
|
{
|
|
memcpy(p, lease->clid, clid_len);
|
|
p += clid_len;
|
|
}
|
|
if (hostname_len != 0)
|
|
{
|
|
memcpy(p, hostname, hostname_len);
|
|
p += hostname_len;
|
|
}
|
|
if (ed_len != 0)
|
|
{
|
|
memcpy(p, lease->extradata, ed_len);
|
|
p += ed_len;
|
|
}
|
|
bytes_in_buf = p - (unsigned char *)buf;
|
|
}
|
|
|
|
#ifdef HAVE_DHCP6
|
|
void queue_relay_snoop(struct in6_addr *client, int if_index, struct in6_addr *prefix, int prefix_len)
|
|
{
|
|
/* no script */
|
|
if (daemon->helperfd == -1)
|
|
return;
|
|
|
|
inet_ntop(AF_INET6, prefix, daemon->addrbuff, ADDRSTRLEN);
|
|
|
|
/* 5 for /nnn and zero on the end of the prefix. */
|
|
buff_alloc(sizeof(struct script_data) + ADDRSTRLEN + 5);
|
|
memset(buf, 0, sizeof(struct script_data));
|
|
|
|
buf->action = ACTION_RELAY_SNOOP;
|
|
buf->addr6 = *client;
|
|
buf->hostname_len = sprintf((char *)(buf+1), "%s/%u", daemon->addrbuff, prefix_len) + 1;
|
|
|
|
indextoname(daemon->dhcp6fd, if_index, buf->interface);
|
|
|
|
bytes_in_buf = sizeof(struct script_data) + buf->hostname_len;
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_TFTP
|
|
/* This nastily re-uses DHCP-fields for TFTP stuff */
|
|
void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer)
|
|
{
|
|
unsigned int filename_len;
|
|
|
|
/* no script */
|
|
if (daemon->helperfd == -1)
|
|
return;
|
|
|
|
filename_len = strlen(filename) + 1;
|
|
buff_alloc(sizeof(struct script_data) + filename_len);
|
|
memset(buf, 0, sizeof(struct script_data));
|
|
|
|
buf->action = ACTION_TFTP;
|
|
buf->hostname_len = filename_len;
|
|
buf->file_len = file_len;
|
|
|
|
if ((buf->flags = peer->sa.sa_family) == AF_INET)
|
|
buf->addr = peer->in.sin_addr;
|
|
else
|
|
buf->addr6 = peer->in6.sin6_addr;
|
|
|
|
memcpy((unsigned char *)(buf+1), filename, filename_len);
|
|
|
|
bytes_in_buf = sizeof(struct script_data) + filename_len;
|
|
}
|
|
#endif
|
|
|
|
void queue_arp(int action, unsigned char *mac, int maclen, int family, union all_addr *addr)
|
|
{
|
|
/* no script */
|
|
if (daemon->helperfd == -1)
|
|
return;
|
|
|
|
buff_alloc(sizeof(struct script_data));
|
|
memset(buf, 0, sizeof(struct script_data));
|
|
|
|
buf->action = action;
|
|
buf->hwaddr_len = maclen;
|
|
buf->hwaddr_type = ARPHRD_ETHER;
|
|
if ((buf->flags = family) == AF_INET)
|
|
buf->addr = addr->addr4;
|
|
else
|
|
buf->addr6 = addr->addr6;
|
|
|
|
memcpy(buf->hwaddr, mac, maclen);
|
|
|
|
bytes_in_buf = sizeof(struct script_data);
|
|
}
|
|
|
|
int helper_buf_empty(void)
|
|
{
|
|
return bytes_in_buf == 0;
|
|
}
|
|
|
|
void helper_write(void)
|
|
{
|
|
ssize_t rc;
|
|
|
|
if (bytes_in_buf == 0)
|
|
return;
|
|
|
|
if ((rc = write(daemon->helperfd, buf, bytes_in_buf)) != -1)
|
|
{
|
|
if (bytes_in_buf != (size_t)rc)
|
|
memmove(buf, buf + rc, bytes_in_buf - rc);
|
|
bytes_in_buf -= rc;
|
|
}
|
|
else
|
|
{
|
|
if (errno == EAGAIN || errno == EINTR)
|
|
return;
|
|
bytes_in_buf = 0;
|
|
}
|
|
}
|
|
|
|
#endif /* HAVE_SCRIPT */
|