ndhc/ndhc/ndhc.c
Nicholas J. Kain d9571c62cf Don't use malloc in ndhc. The only place it was used is in the options code.
Allow the user to specify the vendor identification option value using the
-V switch.  The default value is still "ndhc".
2011-07-02 03:48:08 -04:00

312 lines
9.1 KiB
C

/* ndhc.c - DHCP client
* Time-stamp: <2011-06-11 11:13:32 njk>
*
* (c) 2004-2011 Nicholas J. Kain <njkain at gmail dot com>
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#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 <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <net/if.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include "ndhc-defines.h"
#include "config.h"
#include "state.h"
#include "options.h"
#include "packet.h"
#include "sys.h"
#include "ifchange.h"
#include "arp.h"
#include "netlink.h"
#include "leasefile.h"
#include "log.h"
#include "chroot.h"
#include "cap.h"
#include "strl.h"
#include "pidfile.h"
#include "io.h"
#define VERSION "1.0"
struct client_state_t cs = {
.init = 1,
.epollFd = -1,
.signalFd = -1,
.listenFd = -1,
.arpFd = -1,
.nlFd = -1,
.routerArp = "\0\0\0\0\0\0",
};
struct client_config_t client_config = {
/* Default options. */
.interface = "eth0",
.arp = "\0\0\0\0\0\0",
};
static void show_usage(void)
{
printf(
"Usage: ndhc [OPTIONS]\n\n"
" -c, --clientid=CLIENTID Client identifier\n"
" -H, --hostname=HOSTNAME Client hostname\n"
" -h Alias for -H\n"
" -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 to which the pid will be written\n"
" -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 Change privileges to this user\n"
" -C, --chroot Directory to which udhcp should chroot\n"
" -v, --version Display version\n"
);
exit(EXIT_SUCCESS);
}
static void signal_dispatch()
{
int t, off = 0;
struct signalfd_siginfo si;
again:
t = read(cs.signalFd, (char *)&si + off, sizeof si - off);
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);
break;
case SIGUSR2:
force_release_action(&cs);
break;
case SIGTERM:
log_line("Received SIGTERM. Exiting gracefully.");
exit(EXIT_SUCCESS);
default:
break;
}
}
static void do_work(void)
{
struct epoll_event events[3];
long long last_awake;
int timeout_delta;
cs.epollFd = epoll_create1(0);
if (cs.epollFd == -1)
suicide("epoll_create1 failed");
setup_signals(&cs);
epoll_add(&cs, cs.nlFd);
set_listen_raw(&cs);
timeout_action(&cs);
for (;;) {
last_awake = curms();
int r = epoll_wait(cs.epollFd, events, 3, cs.timeout);
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)
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);
else
suicide("epoll_wait: unknown fd");
}
timeout_delta = curms() - last_awake;
cs.timeout -= timeout_delta;
if (cs.timeout <= 0) {
cs.timeout = 0;
timeout_action(&cs);
}
}
}
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;
static struct option arg_options[] = {
{"clientid", required_argument, 0, 'c'},
{"foreground", no_argument, 0, 'f'},
{"background", no_argument, 0, 'b'},
{"pidfile", required_argument, 0, 'p'},
{"leasefile", required_argument, 0, 'l'},
{"hostname", required_argument, 0, 'H'},
{"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'},
{"version", no_argument, 0, 'v'},
{"vendorid", required_argument, 0, 'V'},
{"user", required_argument, 0, 'u'},
{"chroot", required_argument, 0, 'C'},
{"help", no_argument, 0, '?'},
{0, 0, 0, 0}
};
/* get options */
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "c:fbp:H:h:i:np:l:qr:u:C:vV:", arg_options,
&option_index);
if (c == -1) break;
switch (c) {
case 'c':
strlcpy(client_config.clientid, optarg,
sizeof client_config.clientid);
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':
strlcpy(pidfile, optarg, sizeof pidfile);
break;
case 'l':
set_leasefile(optarg);
break;
case 'h':
case 'H':
strlcpy(client_config.hostname, optarg,
sizeof client_config.hostname);
break;
case 'i':
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':
strlcpy(chroot_dir, optarg, sizeof chroot_dir);
break;
case 'v':
printf("ndhc, version " VERSION "\n\n");
exit(EXIT_SUCCESS);
break;
case 'V':
strlcpy(client_config.vendor, optarg,
sizeof client_config.vendor);
break;
default:
show_usage();
}
}
log_line("ndhc client " 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 (nl_open(&cs) < 0) {
log_line("FATAL - failed to open netlink socket");
exit(EXIT_FAILURE);
}
if (nl_getifdata(client_config.interface, &cs) < 0) {
log_line("FATAL - failed to get interface MAC and index");
exit(EXIT_FAILURE);
}
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);
ifchange(NULL, IFCHANGE_DECONFIG);
do_work();
return EXIT_SUCCESS; // Never reached.
}