ndhc/ndhc/ndhc.c

499 lines
17 KiB
C
Raw Normal View History

/* ndhc.c - DHCP client
2010-11-12 14:32:18 +05:30
*
* Copyright (c) 2004-2013 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
2010-11-12 14:32:18 +05:30
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
2010-11-12 14:32:18 +05:30
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
2010-11-12 14:32:18 +05:30
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
2010-11-12 14:32:18 +05:30
*/
2010-11-12 14:32:18 +05:30
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/file.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
2010-12-01 23:41:09 +05:30
#include <sys/epoll.h>
2010-12-01 23:05:13 +05:30
#include <sys/signalfd.h>
2010-11-12 14:32:18 +05:30
#include <net/if.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include "ndhc-defines.h"
2010-12-24 16:30:37 +05:30
#include "config.h"
#include "state.h"
2010-11-12 14:32:18 +05:30
#include "options.h"
2011-07-02 13:21:44 +05:30
#include "dhcp.h"
#include "sys.h"
2010-12-24 20:42:41 +05:30
#include "ifchange.h"
2010-12-24 20:28:47 +05:30
#include "arp.h"
#include "nl.h"
#include "netlink.h"
2011-04-20 02:07:43 +05:30
#include "leasefile.h"
2010-11-12 14:32:18 +05:30
#include "log.h"
2010-11-12 16:12:07 +05:30
#include "chroot.h"
2010-11-12 19:34:43 +05:30
#include "cap.h"
2010-11-12 16:12:07 +05:30
#include "strl.h"
#include "pidfile.h"
#include "io.h"
#include "seccomp-bpf.h"
2010-11-12 14:32:18 +05:30
struct client_state_t cs = {
.init = 1,
.epollFd = -1,
.signalFd = -1,
.listenFd = -1,
.arpFd = -1,
.nlFd = -1,
.nlPortId = -1,
.routerArp = "\0\0\0\0\0\0",
.serverArp = "\0\0\0\0\0\0",
};
2010-11-12 14:32:18 +05:30
struct client_config_t client_config = {
.interface = "eth0",
.arp = "\0\0\0\0\0\0",
2010-11-12 14:32:18 +05:30
};
static void show_usage(void)
{
printf(
2012-07-23 22:54:15 +05:30
"ndhc " NDHC_VERSION ", dhcp client. Licensed under 2-clause BSD.\n"
"Copyright (C) 2004-2012 Nicholas J. Kain\n"
2010-11-12 14:32:18 +05:30
"Usage: ndhc [OPTIONS]\n\n"
" -c, --clientid=CLIENTID Client identifier\n"
2011-07-05 20:44:35 +05:30
" -h, --hostname=HOSTNAME Client hostname\n"
" -V, --vendorid=VENDORID Client vendor identification string\n"
2010-11-12 14:32:18 +05:30
" -f, --foreground Do not fork after getting lease\n"
" -b, --background Fork to background if lease cannot be\n"
" immediately negotiated.\n"
" -p, --pidfile=FILE File to which the pid will be written\n"
" -l, --leasefile=FILE File to which the lease IP will be written\n"
2010-11-12 14:32:18 +05:30
" -i, --interface=INTERFACE Interface to use (default: eth0)\n"
" -n, --now Exit with failure if lease cannot be\n"
" immediately negotiated.\n"
" -q, --quit Quit after obtaining lease\n"
" -r, --request=IP IP address to request (default: none)\n"
" -u, --user=USER Change privileges to this user\n"
" -C, --chroot=DIR Chroot to this directory\n"
" -d, --relentless-defense Never back off in defending IP against\n"
" conflicting hosts (servers only)\n"
" -w, --arp-probe-wait Time to delay before first ARP probe\n"
" -W, --arp-probe-num Number of ARP probes before lease is ok\n"
" -m, --arp-probe-min Min ms to wait for ARP response\n"
" -M, --arp-probe-max Max ms to wait for ARP response\n"
2010-11-12 14:32:18 +05:30
" -v, --version Display version\n"
);
exit(EXIT_SUCCESS);
2010-11-12 14:32:18 +05:30
}
static int enforce_seccomp(void)
{
struct sock_filter filter[] = {
VALIDATE_ARCHITECTURE,
EXAMINE_SYSCALL,
ALLOW_SYSCALL(sendto), // used for glibc syslog routines
ALLOW_SYSCALL(epoll_wait),
ALLOW_SYSCALL(epoll_ctl),
ALLOW_SYSCALL(read),
ALLOW_SYSCALL(write),
ALLOW_SYSCALL(close),
ALLOW_SYSCALL(recvmsg),
ALLOW_SYSCALL(socket),
ALLOW_SYSCALL(setsockopt),
ALLOW_SYSCALL(fcntl),
ALLOW_SYSCALL(bind),
ALLOW_SYSCALL(open),
ALLOW_SYSCALL(connect),
ALLOW_SYSCALL(getsockname),
2012-07-22 19:19:51 +05:30
// Allowed by vDSO
ALLOW_SYSCALL(getcpu),
ALLOW_SYSCALL(time),
ALLOW_SYSCALL(gettimeofday),
ALLOW_SYSCALL(clock_gettime),
// These are for 'write_leasefile()'
ALLOW_SYSCALL(ftruncate),
ALLOW_SYSCALL(lseek),
ALLOW_SYSCALL(fsync),
// These are for 'background()'
ALLOW_SYSCALL(socketpair),
ALLOW_SYSCALL(clone),
ALLOW_SYSCALL(set_robust_list),
ALLOW_SYSCALL(setsid),
ALLOW_SYSCALL(chdir),
ALLOW_SYSCALL(fstat),
ALLOW_SYSCALL(dup2),
ALLOW_SYSCALL(rt_sigprocmask),
ALLOW_SYSCALL(signalfd4),
ALLOW_SYSCALL(mmap),
ALLOW_SYSCALL(munmap),
ALLOW_SYSCALL(rt_sigreturn),
#ifdef __NR_sigreturn
ALLOW_SYSCALL(sigreturn),
#endif
ALLOW_SYSCALL(exit_group),
ALLOW_SYSCALL(exit),
KILL_PROCESS,
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof filter / sizeof filter[0]),
.filter = filter,
};
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
return -1;
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog))
return -1;
return 0;
}
2010-12-01 23:05:13 +05:30
static void signal_dispatch()
{
int t, off = 0;
struct signalfd_siginfo si;
again:
t = read(cs.signalFd, (char *)&si + off, sizeof si - off);
2010-12-01 23:05:13 +05:30
if (t < sizeof si - off) {
if (t < 0) {
if (t == EAGAIN || t == EWOULDBLOCK || t == EINTR)
goto again;
else
suicide("signalfd read error");
}
off += t;
}
switch (si.ssi_signo) {
case SIGUSR1:
force_renew_action(&cs);
2010-12-01 23:05:13 +05:30
break;
case SIGUSR2:
force_release_action(&cs);
2010-12-01 23:05:13 +05:30
break;
case SIGTERM:
log_line("Received SIGTERM. Exiting gracefully.");
exit(EXIT_SUCCESS);
2010-12-01 23:05:13 +05:30
default:
break;
}
}
static int is_string_hwaddr(char *str, size_t slen)
{
if (slen == 17 && str[2] == ':' && str[5] == ':' && str[8] == ':' &&
str[11] == ':' && str[14] == ':' &&
isxdigit(str[0]) && isxdigit(str[1]) && isxdigit(str[3]) &&
isxdigit(str[4]) && isxdigit(str[6]) && isxdigit(str[7]) &&
isxdigit(str[9]) && isxdigit(str[10]) && isxdigit(str[12]) &&
isxdigit(str[13]) && isxdigit(str[15]) && isxdigit(str[16])
)
return 1;
return 0;
}
static int get_clientid_mac_string(char *str, size_t slen)
{
if (!is_string_hwaddr(str, slen))
return 0;
client_config.clientid[0] = strtol(str, NULL, 16);
client_config.clientid[1] = strtol(str+3, NULL, 16);
client_config.clientid[2] = strtol(str+6, NULL, 16);
client_config.clientid[3] = strtol(str+9, NULL, 16);
client_config.clientid[4] = strtol(str+12, NULL, 16);
client_config.clientid[5] = strtol(str+15, NULL, 16);
client_config.clientid[6] = '\0';
return 1;
}
2010-12-01 23:05:13 +05:30
static void do_work(void)
{
struct epoll_event events[3];
long long nowts;
int timeout;
cs.epollFd = epoll_create1(0);
if (cs.epollFd == -1)
2010-12-01 23:41:09 +05:30
suicide("epoll_create1 failed");
if (enforce_seccomp())
log_line("seccomp filter cannot be installed");
setup_signals(&cs);
epoll_add(&cs, cs.nlFd);
set_listen_raw(&cs);
nowts = curms();
goto jumpstart;
2010-12-01 23:41:09 +05:30
for (;;) {
int r = epoll_wait(cs.epollFd, events, 3, timeout);
2010-12-01 23:41:09 +05:30
if (r == -1) {
if (errno == EINTR)
continue;
else
suicide("epoll_wait failed");
}
for (int i = 0; i < r; ++i) {
int fd = events[i].data.fd;
if (fd == cs.signalFd)
2010-12-01 23:41:09 +05:30
signal_dispatch();
else if (fd == cs.listenFd)
handle_packet(&cs);
else if (fd == cs.arpFd)
handle_arp_response(&cs);
else if (fd == cs.nlFd)
handle_nl_message(&cs);
2010-12-01 23:41:09 +05:30
else
suicide("epoll_wait: unknown fd");
}
for (;;) {
nowts = curms();
long long arp_wake_ts = arp_get_wake_ts();
long long dhcp_wake_ts = dhcp_get_wake_ts();
if (arp_wake_ts == -1) {
if (dhcp_wake_ts != -1) {
timeout = dhcp_wake_ts - nowts;
if (timeout < 0)
timeout = 0;
} else
timeout = -1;
} else {
// If dhcp_wake_ts is -1 then we want to sleep anyway.
timeout = (arp_wake_ts < dhcp_wake_ts ?
arp_wake_ts : dhcp_wake_ts) - nowts;
if (timeout < 0)
timeout = 0;
}
if (!timeout) {
jumpstart:
timeout_action(&cs, nowts);
} else
break;
}
}
2010-11-12 14:32:18 +05:30
}
int main(int argc, char **argv)
{
char chroot_dir[MAX_PATH_LENGTH] = "";
int c;
struct passwd *pwd;
uid_t uid = 0;
gid_t gid = 0;
2011-07-11 15:05:40 +05:30
static const struct option arg_options[] = {
{"clientid", required_argument, 0, 'c'},
{"foreground", no_argument, 0, 'f'},
{"background", no_argument, 0, 'b'},
{"pidfile", required_argument, 0, 'p'},
2011-04-20 02:07:43 +05:30
{"leasefile", required_argument, 0, 'l'},
{"hostname", required_argument, 0, 'h'},
{"interface", required_argument, 0, 'i'},
{"now", no_argument, 0, 'n'},
{"quit", no_argument, 0, 'q'},
{"request", required_argument, 0, 'r'},
{"vendorid", required_argument, 0, 'V'},
{"user", required_argument, 0, 'u'},
{"chroot", required_argument, 0, 'C'},
{"relentless-defense", no_argument, 0, 'd'},
{"arp-probe-wait", required_argument, 0, 'w'},
{"arp-probe-num", required_argument, 0, 'W'},
{"arp-probe-min", required_argument, 0, 'm'},
{"arp-probe-max", required_argument, 0, 'M'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, '?'},
{0, 0, 0, 0}
};
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "c:fbp:h:i:np:l:qr:V:u:C:dw:W:m:M:v",
arg_options, &option_index);
if (c == -1) break;
switch (c) {
case 'c':
if (!get_clientid_mac_string(optarg, strlen(optarg)))
2013-05-06 16:37:54 +05:30
strnkcpy(client_config.clientid, optarg,
sizeof client_config.clientid);
else
client_config.clientid_mac = 1;
break;
case 'f':
client_config.foreground = 1;
gflags_detach = 0;
break;
case 'b':
client_config.background_if_no_lease = 1;
gflags_detach = 1;
break;
case 'p':
2013-05-06 16:37:54 +05:30
strnkcpy(pidfile, optarg, sizeof pidfile);
break;
2011-04-20 02:07:43 +05:30
case 'l':
set_leasefile(optarg);
break;
case 'h':
2013-05-06 16:37:54 +05:30
strnkcpy(client_config.hostname, optarg,
sizeof client_config.hostname);
break;
case 'i':
2010-12-24 21:14:06 +05:30
client_config.interface = optarg;
break;
case 'n':
client_config.abort_if_no_lease = 1;
break;
case 'q':
client_config.quit_after_lease = 1;
break;
case 'r':
cs.clientAddr = inet_addr(optarg);
break;
case 'u':
pwd = getpwnam(optarg);
if (pwd) {
uid = (int)pwd->pw_uid;
gid = (int)pwd->pw_gid;
} else {
printf("Bad username provided.\n");
exit(EXIT_FAILURE);
}
break;
case 'C':
2013-05-06 16:37:54 +05:30
strnkcpy(chroot_dir, optarg, sizeof chroot_dir);
break;
case 'd':
arp_relentless_def = 1;
break;
case 'w':
case 'W': {
int t = atoi(optarg);
if (t < 0)
break;
if (c == 'w')
arp_probe_wait = t;
else
arp_probe_num = t;
break;
}
case 'm':
case 'M': {
int t = atoi(optarg);
if (c == 'm')
arp_probe_min = t;
else
arp_probe_max = t;
if (arp_probe_min > arp_probe_max) {
t = arp_probe_max;
arp_probe_max = arp_probe_min;
arp_probe_min = t;
}
break;
}
case 'v':
2012-07-23 22:54:15 +05:30
printf("ndhc %s, dhcp client.\n", NDHC_VERSION);
printf("Copyright (c) 2004-2013 Nicholas J. Kain\n"
"All rights reserved.\n\n"
"Redistribution and use in source and binary forms, with or without\n"
"modification, are permitted provided that the following conditions are met:\n\n"
"- Redistributions of source code must retain the above copyright notice,\n"
" this list of conditions and the following disclaimer.\n"
"- Redistributions in binary form must reproduce the above copyright notice,\n"
" this list of conditions and the following disclaimer in the documentation\n"
" and/or other materials provided with the distribution.\n\n"
"THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n"
"AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n"
"IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
"ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n"
"LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n"
"CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n"
"SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n"
"INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n"
"CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n"
"ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
"POSSIBILITY OF SUCH DAMAGE.\n");
exit(EXIT_SUCCESS);
break;
case 'V':
2013-05-06 16:37:54 +05:30
strnkcpy(client_config.vendor, optarg,
sizeof client_config.vendor);
break;
default:
show_usage();
}
}
2012-07-23 22:54:15 +05:30
log_line("ndhc client " NDHC_VERSION " started.");
if (client_config.foreground && !client_config.background_if_no_lease) {
if (file_exists(pidfile, "w") == -1) {
log_line("FATAL - cannot open pidfile for write!");
exit(EXIT_FAILURE);
}
write_pid(pidfile);
}
if ((cs.nlFd = nl_open(NETLINK_ROUTE, RTMGRP_LINK, &cs.nlPortId)) < 0) {
log_line("FATAL - failed to open netlink socket");
exit(EXIT_FAILURE);
}
2012-04-04 07:30:47 +05:30
if (nl_getifdata(&cs) < 0) {
log_line("FATAL - failed to get interface MAC and index");
exit(EXIT_FAILURE);
}
2011-04-20 02:07:43 +05:30
open_leasefile();
if (chdir(chroot_dir)) {
printf("Failed to chdir(%s)!\n", chroot_dir);
exit(EXIT_FAILURE);
}
if (chroot(chroot_dir)) {
printf("Failed to chroot(%s)!\n", chroot_dir);
exit(EXIT_FAILURE);
}
set_cap(uid, gid,
"cap_net_bind_service,cap_net_broadcast,cap_net_raw=ep");
drop_root(uid, gid);
if (cs.ifsPrevState != IFS_UP)
2011-07-05 06:21:27 +05:30
ifchange_deconfig();
do_work();
return EXIT_SUCCESS; // Never reached.
2010-11-12 14:32:18 +05:30
}