/*
* Copyright 1999 Red Hat Software, Inc.
*
* 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.
*
*/
/* This probably doesn't work on anything other then Ethernet! It may work
on PLIP as well, but ARCnet and Token Ring are unlikely at best. */
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_packet.h>
#include <net/route.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <popt.h>
#include <resolv.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <syslog.h>
#include <unistd.h>
#include "config.h"
#include "pump.h"
#define N_(foo) (foo)
#define PROGNAME "pump"
#define CONTROLSOCKET "/var/run/pump.sock"
#define _(foo) ((foo))
#include <stdarg.h>
struct command {
enum { CMD_STARTIFACE, CMD_RESULT, CMD_DIE, CMD_STOPIFACE,
CMD_FORCERENEW, CMD_REQSTATUS, CMD_STATUS } type;
union {
struct {
char device[20];
int flags;
int reqLease; /* in hours */
char reqHostname[200];
} start;
int result; /* 0 for success */
struct {
char device[20];
} stop;
struct {
char device[20];
} renew;
struct {
char device[20];
} reqstatus;
struct {
struct pumpNetIntf intf;
char hostname[1024];
char domain[1024];
char bootFile[1024];
} status;
} u;
};
static int openControlSocket(char * configFile);
char * readSearchPath(void) {
int fd;
struct stat sb;
char * buf;
char * start;
fd = open("/etc/resolv.conf", O_RDONLY);
if (fd < 0) return NULL;
fstat(fd, &sb);
buf = alloca(sb.st_size + 2);
if (read(fd, buf, sb.st_size) != sb.st_size) return NULL;
buf[sb.st_size] = '\n';
buf[sb.st_size + 1] = '\0';
close(fd);
start = buf;
while (start && *start) {
while (isspace(*start) && (*start != '\n')) start++;
if (*start == '\n') {
start++;
continue;
}
if (!strncmp("search", start, 6) && isspace(start[6])) {
start += 6;
while (isspace(*start) && *start != '\n') start++;
if (*start == '\n') return NULL;
buf = strchr(start, '\n');
*buf = '\0';
return strdup(start);
}
}
return NULL;
}
static void createResolvConf(struct pumpNetIntf * intf, char * domain,
int isSearchPath) {
FILE * f;
int i;
char * chptr;
/* force a reread of /etc/resolv.conf if we need it again */
res_close();
if (!domain) {
domain = readSearchPath();
if (domain) {
chptr = alloca(strlen(domain) + 1);
strcpy(chptr, domain);
free(domain);
domain = chptr;
isSearchPath = 1;
}
}
f = fopen("/etc/resolv.conf", "w");
if (!f) {
syslog(LOG_ERR, "cannot create /etc/resolv.conf: %s\n",
strerror(errno));
return;
}
if (domain && isSearchPath) {
fprintf(f, "search %s\n", domain);
} else if (domain) {
fprintf(f, "search");
chptr = domain;
do {
fprintf(f, " %s", chptr);
chptr = strchr(chptr, '.');
if (chptr) {
chptr++;
if (!strchr(chptr, '.'))
chptr = NULL;
}
} while (chptr);
fprintf(f, "\n");
}
for (i = 0; i < intf->numDns; i++)
fprintf(f, "nameserver %s\n", inet_ntoa(intf->dnsServers[i]));
fclose(f);
/* force a reread of /etc/resolv.conf */
endhostent();
}
void setupDns(struct pumpNetIntf * intf, struct pumpOverrideInfo * override) {
char * hn, * dn = NULL;
struct hostent * he;
if (override->flags & OVERRIDE_FLAG_NODNS) {
return;
}
if (override->searchPath) {
createResolvConf(intf, override->searchPath, 1);
return;
}
if (intf->set & PUMP_NETINFO_HAS_DNS) {
if (!(intf->set & PUMP_NETINFO_HAS_DOMAIN)) {
if (intf->set & PUMP_NETINFO_HAS_HOSTNAME) {
hn = intf->hostname;
} else {
createResolvConf(intf, NULL, 0);
he = gethostbyaddr((char *) &intf->ip, sizeof(intf->ip),
AF_INET);
if (he) {
hn = he->h_name;
} else {
hn = NULL;
}
}
if (hn) {
dn = strchr(hn, '.');
if (dn)
dn++;
}
} else {
dn = intf->domain;
}
createResolvConf(intf, dn, 0);
}
}
static void runDaemon(int sock, char * configFile) {
int conn;
struct sockaddr_un addr;
int addrLength;
struct command cmd;
struct pumpNetIntf intf[20];
int numInterfaces = 0;
int i;
int closest;
struct timeval tv;
fd_set fds;
struct pumpOverrideInfo * overrides = NULL;
struct pumpOverrideInfo emptyOverride, * o;
readPumpConfig(configFile, &overrides);
if (!overrides) {
overrides = &emptyOverride;
overrides->intf.device[0] = '\0';
}
while (1) {
FD_ZERO(&fds);
FD_SET(sock, &fds);
tv.tv_sec = tv.tv_usec = 0;
closest = -1;
if (numInterfaces) {
for (i = 0; i < numInterfaces; i++)
if ((intf[i].set & PUMP_INTFINFO_HAS_LEASE) &&
(closest == -1 ||
(intf[closest].renewAt > intf[i].renewAt)))
closest = i;
if (closest != -1) {
tv.tv_sec = intf[closest].renewAt - time(NULL);
if (tv.tv_sec <= 0) {
pumpDhcpRenew(intf + closest);
continue;
}
}
}
if (select(sock + 1, &fds, NULL, NULL,
closest != -1 ? &tv : NULL) > 0) {
conn = accept(sock, &addr, &addrLength);
if (read(conn, &cmd, sizeof(cmd)) != sizeof(cmd)) {
close(conn);
continue;
}
switch (cmd.type) {
case CMD_DIE:
for (i = 0; i < numInterfaces; i++)
pumpDhcpRelease(intf + i);
syslog(LOG_INFO, "terminating at root's request");
cmd.type = CMD_RESULT;
cmd.u.result = 0;
write(conn, &cmd, sizeof(cmd));
exit(0);
case CMD_STARTIFACE:
o = overrides;
while (*o->intf.device &&
strcmp(o->intf.device, cmd.u.start.device)) {
o++;
}
if (!*o->intf.device) o = overrides;
if (pumpDhcpRun(cmd.u.start.device,
cmd.u.start.flags, cmd.u.start.reqLease,
cmd.u.start.reqHostname[0] ?
cmd.u.start.reqHostname : NULL,
intf + numInterfaces, o)) {
cmd.u.result = 1;
} else {
pumpSetupInterface(intf);
syslog(LOG_INFO, "configured interface %s", intf->device);
if (intf->set & PUMP_NETINFO_HAS_GATEWAY) {
pumpSetupDefaultGateway(&intf->gateway);
}
setupDns(intf, o);
cmd.u.result = 0;
numInterfaces++;
}
break;
case CMD_FORCERENEW:
for (i = 0; i < numInterfaces; i++)
if (!strcmp(intf[i].device, cmd.u.renew.device)) break;
if (i == numInterfaces)
cmd.u.result = RESULT_UNKNOWNIFACE;
else
cmd.u.result = pumpDhcpRenew(intf + i);
break;
case CMD_STOPIFACE:
for (i = 0; i < numInterfaces; i++)
if (!strcmp(intf[i].device, cmd.u.stop.device)) break;
if (i == numInterfaces)
cmd.u.result = RESULT_UNKNOWNIFACE;
else {
cmd.u.result = pumpDhcpRelease(intf + i);
if (numInterfaces == 1) {
cmd.type = CMD_RESULT;
write(conn, &cmd, sizeof(cmd));
syslog(LOG_INFO, "terminating as there are no "
"more devices under management");
exit(0);
}
intf[i] = intf[numInterfaces - 1];
numInterfaces--;
}
break;
case CMD_REQSTATUS:
for (i = 0; i < numInterfaces; i++)
if (!strcmp(intf[i].device, cmd.u.stop.device)) break;
if (i == numInterfaces) {
cmd.u.result = RESULT_UNKNOWNIFACE;
} else {
cmd.type = CMD_STATUS;
cmd.u.status.intf = intf[i];
if (intf[i].set & PUMP_NETINFO_HAS_HOSTNAME)
strncpy(cmd.u.status.hostname,
intf->hostname, sizeof(cmd.u.status.hostname));
cmd.u.status.hostname[sizeof(cmd.u.status.hostname)] = '\0';
if (intf[i].set & PUMP_NETINFO_HAS_DOMAIN)
strncpy(cmd.u.status.domain,
intf->domain, sizeof(cmd.u.status.domain));
cmd.u.status.domain[sizeof(cmd.u.status.domain) - 1] = '\0';
if (intf[i].set & PUMP_INTFINFO_HAS_BOOTFILE)
strncpy(cmd.u.status.bootFile,
intf->bootFile, sizeof(cmd.u.status.bootFile));
cmd.u.status.bootFile[sizeof(cmd.u.status.bootFile) - 1] =
'\0';
}
case CMD_STATUS:
case CMD_RESULT:
/* can't happen */
break;
}
if (cmd.type != CMD_STATUS) cmd.type = CMD_RESULT;
write(conn, &cmd, sizeof(cmd));
close(conn);
}
}
exit(0);
}
static int openControlSocket(char * configFile) {
struct sockaddr_un addr;
int sock;
size_t addrLength;
pid_t child;
int status;
if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
return -1;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, CONTROLSOCKET);
addrLength = sizeof(addr.sun_family) + strlen(addr.sun_path);
if (!connect(sock, (struct sockaddr *) &addr, addrLength))
return sock;
if (errno != ENOENT && errno != ECONNREFUSED) {
fprintf(stderr, "failed to connect to %s: %s\n", CONTROLSOCKET,
strerror(errno));
close(sock);
return -1;
}
if (!(child = fork())) {
close(sock);
close(0);
close(1);
close(2);
if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
syslog(LOG_ERR, "failed to create socket: %s\n", strerror(errno));
exit(1);
}
unlink(CONTROLSOCKET);
umask(077);
if (bind(sock, (struct sockaddr *) &addr, addrLength)) {
syslog(LOG_ERR, "bind to %s failed: %s\n", CONTROLSOCKET,
strerror(errno));
exit(1);
}
umask(033);
listen(sock, 5);
if (fork()) _exit(0);
openlog("pumpd", LOG_PID, LOG_DAEMON);
{
time_t t;
t = time(NULL);
syslog(LOG_INFO, "starting at %s\n", ctime(&t));
}
runDaemon(sock, configFile);
}
waitpid(child, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status))
return -1;
if (!connect(sock, (struct sockaddr *) &addr, addrLength))
return sock;
fprintf(stderr, "failed to connect to %s: %s\n", CONTROLSOCKET,
strerror(errno));
return 0;
}
void printStatus(struct pumpNetIntf i, char * hostname, char * domain,
char * bootFile) {
int j;
printf("Device %s\n", i.device);
printf("\tIP: %s\n", inet_ntoa(i.ip));
printf("\tNetmask: %s\n", inet_ntoa(i.netmask));
printf("\tBroadcast: %s\n", inet_ntoa(i.broadcast));
printf("\tNetwork: %s\n", inet_ntoa(i.network));
printf("\tBoot server %s\n", inet_ntoa(i.bootServer));
if (i.set & PUMP_NETINFO_HAS_GATEWAY)
printf("\tGateway: %s\n", inet_ntoa(i.gateway));
if (i.set & PUMP_INTFINFO_HAS_BOOTFILE)
printf("\tBoot file: %s\n", bootFile);
if (i.set & PUMP_NETINFO_HAS_HOSTNAME)
printf("\tHostname: %s\n", hostname);
if (i.set & PUMP_NETINFO_HAS_DOMAIN)
printf("\tDomain: %s\n", domain);
if (i.numDns) {
printf("\tNameservers:");
for (j = 0; j < i.numDns; j++)
printf(" %s", inet_ntoa(i.dnsServers[j]));
printf("\n");
}
if (i.set & PUMP_INTFINFO_HAS_LEASE) {
printf("\tRenewal time: %s", ctime(&i.renewAt));
printf("\tExpiration time: %s", ctime(&i.leaseExpiration));
}
}
int main (int argc, char ** argv) {
char * device = "eth0";
char * hostname = "";
poptContext optCon;
int rc;
int test = 0;
int flags = 0;
int lease = 6;
int killDaemon = 0;
int release = 0, renew = 0, status = 0, lookupHostname = 0;
struct command cmd, response;
char * configFile = "/etc/pump.conf";
struct pumpOverrideInfo * overrides;
int cont;
struct poptOption options[] = {
{ "config-file", 'c', POPT_ARG_STRING, &configFile, 0,
N_("Configuration file to use instead of "
"/etc/pump.conf") },
{ "hostname", 'h', POPT_ARG_STRING, &hostname, 0,
N_("Hostname to request"), N_("hostname") },
{ "interface", 'i', POPT_ARG_STRING, &device, 0,
N_("Interface to configure (normally eth0)"),
N_("iface") },
{ "kill", 'k', POPT_ARG_NONE, &killDaemon, 0,
N_("Kill daemon (and disable all interfaces)"), NULL },
{ "lease", 'l', POPT_ARG_INT, &lease, 0,
N_("Lease time to request (in hours)"), N_("hours") },
{ "lookup-hostname", '\0', POPT_ARG_NONE, &lookupHostname, 0,
N_("Force lookup of hostname") },
{ "release", 'r', POPT_ARG_NONE, &release, 0,
N_("Release interface"), NULL },
{ "renew", 'R', POPT_ARG_NONE, &renew, 0,
N_("Force immediate lease renewal"), NULL },
{ "status", 's', POPT_ARG_NONE, &status, 0,
N_("Display interface status"), NULL },
/*{ "test", 't', POPT_ARG_NONE, &test, 0,
N_("Don't change the interface configuration or "
"run as a deamon.") },*/
POPT_AUTOHELP
{ NULL, '\0', 0, NULL, 0 }
};
optCon = poptGetContext(PROGNAME, argc, argv, options,0);
poptReadDefaultConfig(optCon, 1);
if ((rc = poptGetNextOpt(optCon)) < -1) {
fprintf(stderr, _("%s: bad argument %s: %s\n"), PROGNAME,
poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
poptStrerror(rc));
return 1;
}
if (poptGetArg(optCon)) {
fprintf(stderr, _("%s: no extra parameters are expected\n"), PROGNAME);
return 1;
}
/* make sure the config file is parseable before going on any further */
if (readPumpConfig(configFile, &overrides)) return 1;
if (geteuid()) {
fprintf(stderr, _("%s: must be run as root\n"), PROGNAME);
exit(1);
}
if (test)
flags = PUMP_FLAG_NODAEMON | PUMP_FLAG_NOCONFIG;
if (lookupHostname)
flags |= PUMP_FLAG_FORCEHNLOOKUP;
cont = openControlSocket(configFile);
if (cont < 0)
exit(1);
if (killDaemon) {
cmd.type = CMD_DIE;
} else if (status) {
cmd.type = CMD_REQSTATUS;
strcpy(cmd.u.reqstatus.device, device);
} else if (renew) {
cmd.type = CMD_FORCERENEW;
strcpy(cmd.u.renew.device, device);
} else if (release) {
cmd.type = CMD_STOPIFACE;
strcpy(cmd.u.stop.device, device);
} else {
cmd.type = CMD_STARTIFACE;
strcpy(cmd.u.start.device, device);
cmd.u.start.flags = flags;
cmd.u.start.reqLease = lease * 60 * 60;
strcpy(cmd.u.start.reqHostname, hostname);
}
write(cont, &cmd, sizeof(cmd));
read(cont, &response, sizeof(response));
if (response.type == CMD_RESULT && response.u.result &&
cmd.type == CMD_STARTIFACE) {
cont = openControlSocket(configFile);
if (cont < 0)
exit(1);
write(cont, &cmd, sizeof(cmd));
read(cont, &response, sizeof(response));
}
if (response.type == CMD_RESULT) {
if (response.u.result) {
fprintf(stderr, "Operation failed.\n");
return 1;
}
} else if (response.type == CMD_STATUS) {
printStatus(response.u.status.intf, response.u.status.hostname,
response.u.status.domain, response.u.status.bootFile);
}
return 0;
}