#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/dhcp.h>
#include <signal.h>
#include <sys/socket.h>
#include <net/route.h>
#include <net/if_arp.h>
#include <string.h>
#include <dhcpmsg.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <netdb.h>
#include <fcntl.h>
#include <stdio.h>
#include <dhcp_hostconf.h>
#include <dhcp_inittab.h>
#include <dhcp_symbol.h>
#include <limits.h>
#include <strings.h>
#include <libipadm.h>
#include "states.h"
#include "agent.h"
#include "interface.h"
#include "util.h"
#include "packet.h"
#include "defaults.h"
#define ETCNODENAME "/etc/nodename"
static boolean_t is_fqdn(const char *);
static boolean_t dhcp_assemble_fqdn(char *fqdnbuf, size_t buflen,
dhcp_smach_t *dsmp);
const char *
pkt_type_to_string(uchar_t type, boolean_t isv6)
{
static const char *v4types[] = {
"BOOTP", "DISCOVER", "OFFER", "REQUEST", "DECLINE",
"ACK", "NAK", "RELEASE", "INFORM"
};
static const char *v6types[] = {
NULL, "SOLICIT", "ADVERTISE", "REQUEST",
"CONFIRM", "RENEW", "REBIND", "REPLY",
"RELEASE", "DECLINE", "RECONFIGURE", "INFORMATION-REQUEST",
"RELAY-FORW", "RELAY-REPL"
};
if (isv6) {
if (type >= sizeof (v6types) / sizeof (*v6types) ||
v6types[type] == NULL)
return ("<unknown>");
else
return (v6types[type]);
} else {
if (type >= sizeof (v4types) / sizeof (*v4types) ||
v4types[type] == NULL)
return ("<unknown>");
else
return (v4types[type]);
}
}
const char *
monosec_to_string(monosec_t monosec)
{
time_t time = monosec_to_time(monosec);
char *time_string = ctime(&time);
time_string[strlen(time_string) - 1] = '\0';
return (time_string);
}
monosec_t
monosec(void)
{
return (gethrtime() / NANOSEC);
}
time_t
monosec_to_time(monosec_t abs_monosec)
{
return (abs_monosec - monosec()) + time(NULL);
}
monosec_t
hrtime_to_monosec(hrtime_t hrtime)
{
return (hrtime / NANOSEC);
}
void
print_server_msg(dhcp_smach_t *dsmp, const char *msg, uint_t msglen)
{
if (msglen > 0) {
dhcpmsg(MSG_INFO, "%s: message from server: %.*s",
dsmp->dsm_name, msglen, msg);
}
}
static void
alrm_exit(int sig)
{
int exitval;
if (sig == SIGALRM && grandparent != 0)
exitval = EXIT_SUCCESS;
else
exitval = EXIT_FAILURE;
_exit(exitval);
}
int
daemonize(void)
{
if (grandparent != 0)
(void) signal(SIGALRM, alrm_exit);
switch (fork()) {
case -1:
return (0);
case 0:
if (grandparent != 0)
(void) signal(SIGALRM, SIG_DFL);
(void) setsid();
switch (fork()) {
case -1:
return (0);
case 0:
(void) signal(SIGHUP, SIG_IGN);
(void) chdir("/");
(void) umask(022);
closefrom(0);
break;
default:
_exit(EXIT_SUCCESS);
}
break;
default:
if (grandparent != 0) {
(void) signal(SIGCHLD, SIG_IGN);
syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: "
"waiting for adoption to complete.");
if (sleep(DHCP_ADOPT_SLEEP) == 0) {
syslog(LOG_WARNING | LOG_DAEMON,
"dhcpagent: daemonize: timed out awaiting "
"adoption.");
}
syslog(LOG_DEBUG | LOG_DAEMON, "dhcpagent: daemonize: "
"wait finished");
}
_exit(EXIT_SUCCESS);
}
return (1);
}
static boolean_t
update_default_route(uint32_t ifindex, int type, struct in_addr *gateway_nbo,
int flags)
{
struct {
struct rt_msghdr rm_mh;
struct sockaddr_in rm_dst;
struct sockaddr_in rm_gw;
struct sockaddr_in rm_mask;
struct sockaddr_dl rm_ifp;
} rtmsg;
(void) memset(&rtmsg, 0, sizeof (rtmsg));
rtmsg.rm_mh.rtm_version = RTM_VERSION;
rtmsg.rm_mh.rtm_msglen = sizeof (rtmsg);
rtmsg.rm_mh.rtm_type = type;
rtmsg.rm_mh.rtm_pid = getpid();
rtmsg.rm_mh.rtm_flags = RTF_GATEWAY | RTF_STATIC | flags;
rtmsg.rm_mh.rtm_addrs = RTA_GATEWAY | RTA_DST | RTA_NETMASK | RTA_IFP;
rtmsg.rm_gw.sin_family = AF_INET;
rtmsg.rm_gw.sin_addr = *gateway_nbo;
rtmsg.rm_dst.sin_family = AF_INET;
rtmsg.rm_dst.sin_addr.s_addr = htonl(INADDR_ANY);
rtmsg.rm_mask.sin_family = AF_INET;
rtmsg.rm_mask.sin_addr.s_addr = htonl(0);
rtmsg.rm_ifp.sdl_family = AF_LINK;
rtmsg.rm_ifp.sdl_index = ifindex;
return (write(rtsock_fd, &rtmsg, sizeof (rtmsg)) == sizeof (rtmsg));
}
boolean_t
add_default_route(uint32_t ifindex, struct in_addr *gateway_nbo)
{
return (update_default_route(ifindex, RTM_ADD, gateway_nbo, RTF_UP));
}
boolean_t
del_default_route(uint32_t ifindex, struct in_addr *gateway_nbo)
{
if (gateway_nbo->s_addr == htonl(INADDR_ANY))
return (B_TRUE);
return (update_default_route(ifindex, RTM_DELETE, gateway_nbo, 0));
}
void
inactivity_shutdown(iu_tq_t *tqp, void *arg)
{
if (smach_count() > 0)
return;
dhcpmsg(MSG_VERBOSE, "inactivity_shutdown: timed out");
iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL);
}
void
graceful_shutdown(int sig)
{
iu_stop_handling_events(eh, (sig == SIGTERM ? DHCP_REASON_TERMINATE :
DHCP_REASON_SIGNAL), drain_script, NULL);
}
boolean_t
bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo)
{
struct sockaddr_in sin;
int on = 1;
(void) memset(&sin, 0, sizeof (struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_port = htons(port_hbo);
sin.sin_addr.s_addr = htonl(addr_hbo);
(void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0);
}
boolean_t
bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo)
{
struct sockaddr_in6 sin6;
int on = 1;
(void) memset(&sin6, 0, sizeof (struct sockaddr_in6));
sin6.sin6_family = AF_INET6;
sin6.sin6_port = htons(port_hbo);
if (addr_nbo != NULL) {
(void) memcpy(&sin6.sin6_addr, addr_nbo,
sizeof (sin6.sin6_addr));
}
(void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0);
}
#define IFLINE_MAX 1024
const char *
iffile_to_hostname(const char *path)
{
FILE *fp;
static char ifline[IFLINE_MAX];
fp = fopen(path, "r");
if (fp == NULL)
return (NULL);
while (fgets(ifline, sizeof (ifline), fp) != NULL) {
char *p;
if ((p = strstr(ifline, "inet")) != NULL) {
if ((p != ifline) && !isspace(p[-1])) {
(void) fclose(fp);
return (NULL);
}
p += 4;
if ((*p == '\n') || (*p == '\0')) {
(void) fclose(fp);
return (NULL);
}
if (isspace(*p)) {
char *nlptr;
(void) fclose(fp);
while (isspace(*p))
p++;
if ((nlptr = strrchr(p, '\n')) != NULL)
*nlptr = '\0';
if (strlen(p) > MAXHOSTNAMELEN) {
dhcpmsg(MSG_WARNING,
"iffile_to_hostname:"
" host name too long");
return (NULL);
}
if (ipadm_is_valid_hostname(p)) {
return (p);
} else {
dhcpmsg(MSG_WARNING,
"iffile_to_hostname:"
" host name not valid");
return (NULL);
}
} else {
(void) fclose(fp);
return (NULL);
}
}
}
(void) fclose(fp);
return (NULL);
}
void
init_timer(dhcp_timer_t *dt, lease_t startval)
{
dt->dt_id = -1;
dt->dt_start = startval;
}
boolean_t
cancel_timer(dhcp_timer_t *dt)
{
if (dt->dt_id == -1)
return (B_TRUE);
if (iu_cancel_timer(tq, dt->dt_id, NULL) == 1) {
dt->dt_id = -1;
return (B_TRUE);
}
return (B_FALSE);
}
boolean_t
schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg)
{
if (dt->dt_id != -1)
return (B_FALSE);
dt->dt_id = iu_schedule_timer(tq, dt->dt_start, cbfunc, arg);
return (dt->dt_id != -1);
}
int
dhcpv6_status_code(const dhcpv6_option_t *d6o, uint_t olen, const char **estr,
const char **msg, uint_t *msglenp)
{
uint16_t status;
static const char *v6_status[] = {
NULL,
"Unknown reason",
"Server has no addresses available",
"Client record unavailable",
"Prefix inappropriate for link",
"Client must use multicast",
"No prefix available"
};
static char sbuf[32];
*estr = "";
*msg = "";
*msglenp = 0;
if (d6o == NULL)
return (0);
olen -= sizeof (*d6o);
if (olen < 2) {
*estr = "garbled status code";
return (-1);
}
*msg = (const char *)(d6o + 1) + 2;
*msglenp = olen - 2;
(void) memcpy(&status, d6o + 1, sizeof (status));
status = ntohs(status);
if (status > 0) {
if (status > DHCPV6_STAT_NOPREFIX) {
(void) snprintf(sbuf, sizeof (sbuf), "status %u",
status);
*estr = sbuf;
} else {
*estr = v6_status[status];
}
}
return (status);
}
void
write_lease_to_hostconf(dhcp_smach_t *dsmp)
{
PKT_LIST *plp[2];
const char *hcfile;
hcfile = ifname_to_hostconf(dsmp->dsm_name, dsmp->dsm_isv6);
plp[0] = dsmp->dsm_ack;
plp[1] = dsmp->dsm_orig_ack;
if (write_hostconf(dsmp->dsm_name, plp, 2,
monosec_to_time(dsmp->dsm_curstart_monosec),
dsmp->dsm_isv6) != -1) {
dhcpmsg(MSG_DEBUG, "wrote lease to %s", hcfile);
} else if (errno == EROFS) {
dhcpmsg(MSG_DEBUG, "%s is on a read-only file "
"system; not saving lease", hcfile);
} else {
dhcpmsg(MSG_ERR, "cannot write %s (reboot will "
"not use cached configuration)", hcfile);
}
}
static boolean_t
dhcp_get_oneline(const char *filename, char *buf, size_t buflen)
{
char value[SYS_NMLN], *c;
int fd, i;
if ((fd = open(filename, O_RDONLY)) <= 0) {
dhcpmsg(MSG_DEBUG, "dhcp_get_oneline: could not open %s",
filename);
*buf = '\0';
} else {
if ((i = read(fd, value, SYS_NMLN - 1)) <= 0) {
dhcpmsg(MSG_WARNING, "dhcp_get_oneline: no line in %s",
filename);
*buf = '\0';
} else {
value[i] = '\0';
if ((c = strchr(value, '\n')) != NULL)
*c = '\0';
if ((c = strchr(value, ' ')) != NULL)
*c = '\0';
if (strlcpy(buf, value, buflen) >= buflen) {
dhcpmsg(MSG_WARNING, "dhcp_get_oneline: too"
" long value, %s", value);
*buf = '\0';
}
}
(void) close(fd);
}
return (*buf != '\0');
}
static boolean_t
dhcp_get_nodename(char *buf, size_t buflen)
{
return (dhcp_get_oneline(ETCNODENAME, buf, buflen));
}
boolean_t
dhcp_add_hostname_opt(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp)
{
const char *reqhost;
char nodename[MAXNAMELEN];
if (!df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_REQUEST_HOSTNAME))
return (B_FALSE);
dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: DF_REQUEST_HOSTNAME");
if (dsmp->dsm_msg_reqhost != NULL &&
ipadm_is_valid_hostname(dsmp->dsm_msg_reqhost)) {
reqhost = dsmp->dsm_msg_reqhost;
} else {
char hostfile[PATH_MAX + 1];
(void) snprintf(hostfile, sizeof (hostfile),
"/etc/hostname.%s", dsmp->dsm_name);
reqhost = iffile_to_hostname(hostfile);
}
if (reqhost == NULL && (dsmp->dsm_dflags & DHCP_IF_PRIMARY) &&
dhcp_get_nodename(nodename, sizeof (nodename))) {
reqhost = nodename;
}
if (reqhost != NULL) {
free(dsmp->dsm_reqhost);
if ((dsmp->dsm_reqhost = strdup(reqhost)) == NULL)
dhcpmsg(MSG_WARNING, "dhcp_add_hostname_opt: cannot"
" allocate memory for host name option");
}
if (dsmp->dsm_reqhost != NULL) {
dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: host %s for %s",
dsmp->dsm_reqhost, dsmp->dsm_name);
(void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost,
strlen(dsmp->dsm_reqhost));
return (B_FALSE);
} else {
dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: no hostname for %s",
dsmp->dsm_name);
}
return (B_TRUE);
}
boolean_t
dhcp_add_fqdn_opt(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp)
{
const uint8_t S_BIT_POS = 7;
const uint8_t E_BIT_POS = 5;
const uint8_t S_BIT = 1 << (7 - S_BIT_POS);
const uint8_t E_BIT = 1 << (7 - E_BIT_POS);
const size_t OPT_FQDN_METALEN = 3;
char fqdnbuf[MAXNAMELEN];
uchar_t enc_fqdnbuf[MAXNAMELEN];
uint8_t fqdnopt[MAXNAMELEN + OPT_FQDN_METALEN];
uint_t fqdncode;
size_t len, metalen;
if (dsmp->dsm_isv6)
return (B_FALSE);
if (!dhcp_assemble_fqdn(fqdnbuf, sizeof (fqdnbuf), dsmp))
return (B_FALSE);
if (ns_name_pton2(fqdnbuf, enc_fqdnbuf, sizeof (enc_fqdnbuf),
&len) < 0) {
dhcpmsg(MSG_WARNING, "dhcp_add_fqdn_opt: error encoding domain"
" name %s", fqdnbuf);
return (B_FALSE);
}
dhcpmsg(MSG_DEBUG, "dhcp_add_fqdn_opt: interface FQDN is %s"
" for %s", fqdnbuf, dsmp->dsm_name);
bzero(fqdnopt, sizeof (fqdnopt));
fqdncode = CD_CLIENTFQDN;
metalen = OPT_FQDN_METALEN;
*fqdnopt = S_BIT | E_BIT;
(void) memcpy(fqdnopt + metalen, enc_fqdnbuf, len);
(void) add_pkt_opt(dpkt, fqdncode, fqdnopt, metalen + len);
return (B_TRUE);
}
static boolean_t
dhcp_adopt_domainname(char *namebuf, size_t buflen, dhcp_smach_t *dsmp)
{
const char *domainname;
struct __res_state res_state;
int lasterrno;
domainname = dsmp->dsm_dhcp_domainname;
if (ipadm_is_nil_hostname(domainname)) {
bzero(&res_state, sizeof (struct __res_state));
if ((lasterrno = res_ninit(&res_state)) != 0) {
dhcpmsg(MSG_WARNING, "dhcp_adopt_domainname: error %d"
" initializing resolver", lasterrno);
return (B_FALSE);
}
domainname = NULL;
if (!ipadm_is_nil_hostname(res_state.defdname))
domainname = res_state.defdname;
res_ndestroy(&res_state);
}
if (domainname == NULL)
return (B_FALSE);
if (strlcpy(namebuf, domainname, buflen) >= buflen) {
dhcpmsg(MSG_WARNING,
"dhcp_adopt_domainname: too long adopted domain"
" name %s for %s", domainname, dsmp->dsm_name);
return (B_FALSE);
}
return (B_TRUE);
}
static boolean_t
dhcp_pick_domainname(char *namebuf, size_t buflen, dhcp_smach_t *dsmp)
{
const char *domainname;
domainname = df_get_string(dsmp->dsm_name, dsmp->dsm_isv6,
DF_DNS_DOMAINNAME);
if (!ipadm_is_nil_hostname(domainname)) {
if (strlcpy(namebuf, domainname, buflen) >= buflen) {
dhcpmsg(MSG_WARNING, "dhcp_pick_domainname: too long"
" DNS_DOMAINNAME %s for %s", domainname,
dsmp->dsm_name);
return (B_FALSE);
}
return (B_TRUE);
} else if (df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6,
DF_ADOPT_DOMAINNAME)) {
return (dhcp_adopt_domainname(namebuf, buflen, dsmp));
} else {
return (B_FALSE);
}
}
static boolean_t
dhcp_assemble_fqdn(char *fqdnbuf, size_t buflen, dhcp_smach_t *dsmp)
{
char nodename[MAXNAMELEN], *reqhost;
size_t pos, len;
if (!df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_REQUEST_FQDN))
return (B_FALSE);
dhcpmsg(MSG_DEBUG, "dhcp_assemble_fqdn: DF_REQUEST_FQDN");
bzero(fqdnbuf, buflen);
reqhost = dsmp->dsm_msg_reqhost;
if (ipadm_is_nil_hostname(reqhost) &&
(dsmp->dsm_dflags & DHCP_IF_PRIMARY) &&
dhcp_get_nodename(nodename, sizeof (nodename))) {
reqhost = nodename;
}
if (ipadm_is_nil_hostname(reqhost)) {
dhcpmsg(MSG_DEBUG,
"dhcp_assemble_fqdn: no interface reqhost for %s",
dsmp->dsm_name);
return (B_FALSE);
}
if ((pos = strlcpy(fqdnbuf, reqhost, buflen)) >= buflen) {
dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: too long reqhost %s"
" for %s", reqhost, dsmp->dsm_name);
return (B_FALSE);
}
if (!is_fqdn(reqhost)) {
char domainname[MAXNAMELEN];
size_t needdots;
if (!dhcp_pick_domainname(domainname, sizeof (domainname),
dsmp)) {
dhcpmsg(MSG_DEBUG,
"dhcp_assemble_fqdn: no domain name for %s",
dsmp->dsm_name);
return (B_FALSE);
}
len = strlen(domainname);
needdots = 1 + (domainname[len - 1] != '.');
if (pos + len + needdots >= buflen) {
dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: too long"
" FQDN %s.%s for %s", fqdnbuf, domainname,
dsmp->dsm_name);
return (B_FALSE);
}
fqdnbuf[pos++] = '.';
if (strlcpy(fqdnbuf + pos, domainname, buflen - pos) >=
buflen - pos) {
return (B_FALSE);
}
pos += len;
if (needdots > 1)
fqdnbuf[pos++] = '.';
}
if (!ipadm_is_valid_hostname(fqdnbuf)) {
dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: invalid FQDN %s"
" for %s", fqdnbuf, dsmp->dsm_name);
return (B_FALSE);
}
return (B_TRUE);
}
boolean_t
is_fqdn(const char *hostname)
{
const char *c;
size_t i;
if (hostname == NULL)
return (B_FALSE);
i = strlen(hostname);
if (i > 0 && hostname[i - 1] == '.')
return (B_TRUE);
c = hostname;
i = 0;
while ((c = strchr(c, '.')) != NULL) {
++i;
++c;
}
return (i >= 2);
}
static void
terminate_at_space(char *value)
{
if (value != NULL) {
char *sp;
sp = strchr(value, ' ');
if (sp != NULL)
*sp = '\0';
}
}
static char *
get_offered_domainname_v4(PKT_LIST *offer)
{
char *domainname = NULL;
DHCP_OPT *opt;
if ((opt = offer->opts[CD_DNSDOMAIN]) != NULL) {
uchar_t *valptr;
dhcp_symbol_t *symp;
valptr = (uchar_t *)opt + DHCP_OPT_META_LEN;
symp = inittab_getbycode(
ITAB_CAT_STANDARD, ITAB_CONS_INFO, opt->code);
if (symp != NULL) {
domainname = inittab_decode(symp, valptr,
opt->len, B_TRUE);
terminate_at_space(domainname);
free(symp);
}
}
return (domainname);
}
void
save_domainname(dhcp_smach_t *dsmp, PKT_LIST *offer)
{
char *domainname = NULL;
free(dsmp->dsm_dhcp_domainname);
dsmp->dsm_dhcp_domainname = NULL;
if (!dsmp->dsm_isv6) {
domainname = get_offered_domainname_v4(offer);
}
dsmp->dsm_dhcp_domainname = domainname;
}