diff options
Diffstat (limited to 'dhcpcd.c')
-rw-r--r-- | dhcpcd.c | 2652 |
1 files changed, 1819 insertions, 833 deletions
@@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright 2006-2008 Roy Marples <roy@marples.name> + * Copyright (c) 2006-2010 Roy Marples <roy@marples.name> * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -25,123 +25,108 @@ * SUCH DAMAGE. */ -const char copyright[] = "Copyright (c) 2006-2008 Roy Marples"; +const char copyright[] = "Copyright (c) 2006-2010 Roy Marples"; #include <sys/file.h> +#include <sys/socket.h> #include <sys/stat.h> +#include <sys/time.h> #include <sys/types.h> +#include <sys/uio.h> #include <arpa/inet.h> +#include <net/route.h> + +#ifdef __linux__ +# include <asm/types.h> /* for systems with broken headers */ +# include <linux/rtnetlink.h> +#endif #include <ctype.h> #include <errno.h> #include <getopt.h> +#include <limits.h> #include <paths.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <syslog.h> #include <unistd.h> #include <time.h> +#include "arp.h" +#include "bind.h" #include "config.h" -#include "client.h" +#include "common.h" +#include "configure.h" +#include "control.h" #include "dhcpcd.h" -#include "dhcp.h" +#include "duid.h" +#include "eloop.h" +#include "if-options.h" +#include "if-pref.h" +#include "ipv4ll.h" #include "net.h" -#include "logger.h" - -#ifdef ANDROID -#include <linux/capability.h> -#include <linux/prctl.h> -#include <cutils/properties.h> -#include <private/android_filesystem_config.h> -#endif - -/* Don't set any optional arguments here so we retain POSIX - * compatibility with getopt */ -#define OPTS "bc:df:h:i:kl:m:no:pqr:s:t:u:v:xABC:DEF:GI:KLO:Q:TVX:" - -static int doversion = 0; -static int dohelp = 0; -static const struct option longopts[] = { - {"background", no_argument, NULL, 'b'}, - {"script", required_argument, NULL, 'c'}, - {"debug", no_argument, NULL, 'd'}, - {"config", required_argument, NULL, 'f'}, - {"hostname", optional_argument, NULL, 'h'}, - {"vendorclassid", optional_argument, NULL, 'i'}, - {"release", no_argument, NULL, 'k'}, - {"leasetime", required_argument, NULL, 'l'}, - {"metric", required_argument, NULL, 'm'}, - {"rebind", no_argument, NULL, 'n'}, - {"option", required_argument, NULL, 'o'}, - {"persistent", no_argument, NULL, 'p'}, - {"quiet", no_argument, NULL, 'q'}, - {"request", optional_argument, NULL, 'r'}, - {"inform", optional_argument, NULL, 's'}, - {"timeout", required_argument, NULL, 't'}, - {"userclass", required_argument, NULL, 'u'}, - {"vendor", required_argument, NULL, 'v'}, - {"exit", no_argument, NULL, 'x'}, - {"noarp", no_argument, NULL, 'A'}, - {"nobackground", no_argument, NULL, 'B'}, - {"nohook", required_argument, NULL, 'C'}, - {"duid", no_argument, NULL, 'D'}, - {"lastlease", no_argument, NULL, 'E'}, - {"fqdn", optional_argument, NULL, 'F'}, - {"nogateway", no_argument, NULL, 'G'}, - {"clientid", optional_argument, NULL, 'I'}, - {"nolink", no_argument, NULL, 'K'}, - {"noipv4ll", no_argument, NULL, 'L'}, - {"nooption", optional_argument, NULL, 'O'}, - {"require", required_argument, NULL, 'Q'}, - {"test", no_argument, NULL, 'T'}, - {"variables", no_argument, NULL, 'V'}, - {"blacklist", required_argument, NULL, 'X'}, - {"help", no_argument, &dohelp, 1}, - {"version", no_argument, &doversion, 1}, -#ifdef CMDLINE_COMPAT - {"classid", optional_argument, NULL, 'i'}, - {"renew", no_argument, NULL, 'n'}, - {"nohostname", no_argument, NULL, 'H'}, - {"nomtu", no_argument, NULL, 'M'}, - {"nontp", no_argument, NULL, 'N'}, - {"nodns", no_argument, NULL, 'R'}, - {"msscr", no_argument, NULL, 'S'}, - {"nonis", no_argument, NULL, 'Y'}, -#endif - {NULL, 0, NULL, '\0'} +#include "signals.h" + +/* We should define a maximum for the NAK exponential backoff */ +#define NAKOFF_MAX 60 + +/* Wait N nanoseconds between sending a RELEASE and dropping the address. + * This gives the kernel enough time to actually send it. */ +#define RELEASE_DELAY_S 0 +#define RELEASE_DELAY_NS 10000000 + +int options = 0; +int pidfd = -1; +struct interface *ifaces = NULL; +int ifac = 0; +char **ifav = NULL; +int ifdc = 0; +char **ifdv = NULL; + +static char **margv; +static int margc; +static struct if_options *if_options; +static char **ifv; +static int ifc; +static char *cffile; +static char *pidfile; +static int linkfd = -1; + +struct dhcp_op { + uint8_t value; + const char *name; }; -#ifdef CMDLINE_COMPAT -# define EXTRA_OPTS "HMNRSY" -#endif +static const struct dhcp_op dhcp_ops[] = { + { DHCP_DISCOVER, "DISCOVER" }, + { DHCP_OFFER, "OFFER" }, + { DHCP_REQUEST, "REQUEST" }, + { DHCP_DECLINE, "DECLINE" }, + { DHCP_ACK, "ACK" }, + { DHCP_NAK, "NAK" }, + { DHCP_RELEASE, "RELEASE" }, + { DHCP_INFORM, "INFORM" }, + { 0, NULL } +}; -#ifndef EXTRA_OPTS -# define EXTRA_OPTS -#endif +static void send_release(struct interface *); -static int -atoint(const char *s) +static const char * +get_dhcp_op(uint8_t type) { - char *t; - long n; - - errno = 0; - n = strtol(s, &t, 0); - if ((errno != 0 && n == 0) || s == t || - (errno == ERANGE && (n == LONG_MAX || n == LONG_MIN))) - { - logger(LOG_ERR, "`%s' out of range", s); - return -1; - } + const struct dhcp_op *d; - return (int)n; + for (d = dhcp_ops; d->name; d++) + if (d->value == type) + return d->name; + return NULL; } static pid_t -read_pid(const char *pidfile) +read_pid(void) { FILE *fp; pid_t pid; @@ -150,661 +135,1606 @@ read_pid(const char *pidfile) errno = ENOENT; return 0; } - if (fscanf(fp, "%d", &pid) != 1) pid = 0; fclose(fp); - return pid; } static void usage(void) { - printf("usage: "PACKAGE" [-dknpqxADEGHKLOTV] [-c script] [-f file ] [-h hostname]\n" - " [-i classID ] [-l leasetime] [-m metric] [-o option] [-r ipaddr]\n" - " [-s ipaddr] [-t timeout] [-u userclass] [-F none|ptr|both]\n" - " [-I clientID] [-C hookscript] [-Q option] [-X ipaddr] <interface>\n"); + printf("usage: "PACKAGE" [-dgknpqwxyADEGHJKLOTV] [-c script] [-f file]" + " [-e var=val]\n" + " [-h hostname] [-i classID ] [-l leasetime]" + " [-m metric] [-o option]\n" + " [-r ipaddr] [-s ipaddr] [-t timeout]" + " [-u userclass]\n" + " [-F none|ptr|both] [-I clientID] [-C hookscript]" + " [-Q option]\n" + " [-X ipaddr] <interface>\n"); } -static char * -add_environ(struct options *options, const char *value, int uniq) +static void +cleanup(void) { - char **newlist; - char **lst = options->environ; - size_t i = 0, l, lv; - char *match = NULL, *p; - - match = xstrdup(value); - p = strchr(match, '='); - if (p) - *p++ = '\0'; - l = strlen(match); - - while (lst && lst[i]) { - if (match && strncmp(lst[i], match, l) == 0) { - if (uniq) { - free(lst[i]); - lst[i] = xstrdup(value); - } else { - /* Append a space and the value to it */ - l = strlen(lst[i]); - lv = strlen(p); - lst[i] = xrealloc(lst[i], l + lv + 2); - lst[i][l] = ' '; - memcpy(lst[i] + l + 1, p, lv); - lst[i][l + lv + 1] = '\0'; - } - free(match); - return lst[i]; +#ifdef DEBUG_MEMORY + struct interface *iface; + int i; + + free_options(if_options); + + while (ifaces) { + iface = ifaces; + ifaces = iface->next; + free_interface(iface); + } + + for (i = 0; i < ifac; i++) + free(ifav[i]); + free(ifav); + for (i = 0; i < ifdc; i++) + free(ifdv[i]); + free(ifdv); +#endif + + if (linkfd != -1) + close(linkfd); + if (pidfd > -1) { + if (options & DHCPCD_MASTER) { + if (stop_control() == -1) + syslog(LOG_ERR, "stop_control: %m"); } - i++; + close(pidfd); + unlink(pidfile); } +#ifdef DEBUG_MEMORY + free(pidfile); +#endif +} + +/* ARGSUSED */ +void +handle_exit_timeout(_unused void *arg) +{ + int timeout; - newlist = xrealloc(lst, sizeof(char *) * (i + 2)); - newlist[i] = xstrdup(value); - newlist[i + 1] = NULL; - options->environ = newlist; - free(match); - return newlist[i]; + syslog(LOG_ERR, "timed out"); + if (!(options & DHCPCD_TIMEOUT_IPV4LL)) { + if (options & DHCPCD_MASTER) { + daemonise(); + return; + } else + exit(EXIT_FAILURE); + } + options &= ~DHCPCD_TIMEOUT_IPV4LL; + timeout = (PROBE_NUM * PROBE_MAX) + PROBE_WAIT + 1; + syslog(LOG_WARNING, "allowing %d seconds for IPv4LL timeout", timeout); + add_timeout_sec(timeout, handle_exit_timeout, NULL); } -#define parse_string(buf, len, arg) parse_string_hwaddr(buf, len, arg, 0) -static ssize_t -parse_string_hwaddr(char *sbuf, ssize_t slen, char *str, int clid) +void +drop_config(struct interface *iface, const char *reason) { - ssize_t l; - char *p; - int i; - char c[4]; - - /* If surrounded by quotes then it's a string */ - if (*str == '"') { - str++; - l = strlen(str); - p = str + l - 1; - if (*p == '"') - *p = '\0'; + free(iface->state->old); + iface->state->old = iface->state->new; + iface->state->new = NULL; + iface->state->reason = reason; + configure(iface); + free(iface->state->old); + iface->state->old = NULL; + iface->state->lease.addr.s_addr = 0; +} + +struct interface * +find_interface(const char *ifname) +{ + struct interface *ifp; + + for (ifp = ifaces; ifp; ifp = ifp->next) + if (strcmp(ifp->name, ifname) == 0) + return ifp; + return NULL; +} + +static void +stop_interface(struct interface *iface) +{ + struct interface *ifp, *ifl = NULL; + + syslog(LOG_INFO, "%s: removing interface", iface->name); + if (strcmp(iface->state->reason, "RELEASE") != 0) + drop_config(iface, "STOP"); + close_sockets(iface); + delete_timeout(NULL, iface); + for (ifp = ifaces; ifp; ifp = ifp->next) { + if (ifp == iface) + break; + ifl = ifp; + } + if (ifl) + ifl->next = ifp->next; + else + ifaces = ifp->next; + free_interface(ifp); + if (!(options & (DHCPCD_MASTER | DHCPCD_TEST))) + exit(EXIT_FAILURE); +} + +static uint32_t +dhcp_xid(struct interface *iface) +{ + uint32_t xid; + + if (iface->state->options->options & DHCPCD_XID_HWADDR && + iface->hwlen >= sizeof(xid)) + /* The lower bits are probably more unique on the network */ + memcpy(&xid, (iface->hwaddr + iface->hwlen) - sizeof(xid), + sizeof(xid)); + else + xid = arc4random(); + + return xid; +} + +static void +send_message(struct interface *iface, int type, + void (*callback)(void *)) +{ + struct if_state *state = iface->state; + struct if_options *ifo = state->options; + struct dhcp_message *dhcp; + uint8_t *udp; + ssize_t len, r; + struct in_addr from, to; + in_addr_t a = 0; + struct timeval tv; + + if (!callback) + syslog(LOG_DEBUG, "%s: sending %s with xid 0x%x", + iface->name, get_dhcp_op(type), state->xid); + else { + if (state->interval == 0) + state->interval = 4; + else { + state->interval *= 2; + if (state->interval > 64) + state->interval = 64; + } + tv.tv_sec = state->interval + DHCP_RAND_MIN; + tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); + syslog(LOG_DEBUG, + "%s: sending %s (xid 0x%x), next in %0.2f seconds", + iface->name, get_dhcp_op(type), state->xid, + timeval_to_double(&tv)); + } + + /* Ensure sockets are open. */ + open_sockets(iface); + + /* If we couldn't open a UDP port for our IP address + * then we cannot renew. + * This could happen if our IP was pulled out from underneath us. + * Also, we should not unicast from a BOOTP lease. */ + if (iface->udp_fd == -1 || + (!(ifo->options & DHCPCD_INFORM) && is_bootp(iface->state->new))) + { + a = iface->addr.s_addr; + iface->addr.s_addr = 0; + } + len = make_message(&dhcp, iface, type); + if (a) + iface->addr.s_addr = a; + from.s_addr = dhcp->ciaddr; + if (from.s_addr) + to.s_addr = state->lease.server.s_addr; + else + to.s_addr = 0; + if (to.s_addr && to.s_addr != INADDR_BROADCAST) { + r = send_packet(iface, to, (uint8_t *)dhcp, len); + if (r == -1) { + syslog(LOG_ERR, "%s: send_packet: %m", iface->name); + close_sockets(iface); + } } else { - l = hwaddr_aton(NULL, str); - if (l > 1) { - if (l > slen) { - errno = ENOBUFS; - return -1; - } - hwaddr_aton((uint8_t *)sbuf, str); - return l; + len = make_udp_packet(&udp, (uint8_t *)dhcp, len, from, to); + r = send_raw_packet(iface, ETHERTYPE_IP, udp, len); + free(udp); + /* If we failed to send a raw packet this normally means + * we don't have the ability to work beneath the IP layer + * for this interface. + * As such we remove it from consideration without actually + * stopping the interface. */ + if (r == -1) { + syslog(LOG_ERR, "%s: send_raw_packet: %m", iface->name); + if (!(options & DHCPCD_TEST)) + drop_config(iface, "FAIL"); + close_sockets(iface); + delete_timeout(NULL, iface); + callback = NULL; } } + free(dhcp); + + /* Even if we fail to send a packet we should continue as we are + * as our failure timeouts will change out codepath when needed. */ + if (callback) + add_timeout_tv(&tv, callback, iface); +} + +static void +send_inform(void *arg) +{ + send_message((struct interface *)arg, DHCP_INFORM, send_inform); +} - /* Process escapes */ - l = 0; - /* If processing a string on the clientid, first byte should be - * 0 to indicate a non hardware type */ - if (clid && *str) { - *sbuf++ = 0; - l++; +static void +send_discover(void *arg) +{ + send_message((struct interface *)arg, DHCP_DISCOVER, send_discover); +} + +static void +send_request(void *arg) +{ + send_message((struct interface *)arg, DHCP_REQUEST, send_request); +} + +static void +send_renew(void *arg) +{ + send_message((struct interface *)arg, DHCP_REQUEST, send_renew); +} + +static void +send_rebind(void *arg) +{ + send_message((struct interface *)arg, DHCP_REQUEST, send_rebind); +} + +void +start_expire(void *arg) +{ + struct interface *iface = arg; + + iface->state->interval = 0; + if (iface->addr.s_addr == 0) { + /* We failed to reboot, so enter discovery. */ + iface->state->lease.addr.s_addr = 0; + start_discover(iface); + return; } - c[3] = '\0'; - while (*str) { - if (++l > slen) { - errno = ENOBUFS; - return -1; + + syslog(LOG_ERR, "%s: lease expired", iface->name); + delete_timeout(NULL, iface); + drop_config(iface, "EXPIRE"); + unlink(iface->leasefile); + if (iface->carrier != LINK_DOWN) + start_interface(iface); +} + +static void +log_dhcp(int lvl, const char *msg, + const struct interface *iface, const struct dhcp_message *dhcp, + const struct in_addr *from) +{ + const char *tfrom; + char *a; + struct in_addr addr; + int r; + + if (strcmp(msg, "NAK:") == 0) + a = get_option_string(dhcp, DHO_MESSAGE); + else if (dhcp->yiaddr != 0) { + addr.s_addr = dhcp->yiaddr; + a = xstrdup(inet_ntoa(addr)); + } else + a = NULL; + + tfrom = "from"; + r = get_option_addr(&addr, dhcp, DHO_SERVERID); + if (dhcp->servername[0] && r == 0) + syslog(lvl, "%s: %s %s %s %s `%s'", iface->name, msg, a, + tfrom, inet_ntoa(addr), dhcp->servername); + else { + if (r != 0) { + tfrom = "via"; + addr = *from; } - if (*str == '\\') { - str++; - switch(*str++) { - case '\0': - break; - case 'b': - *sbuf++ = '\b'; - break; - case 'n': - *sbuf++ = '\n'; - break; - case 'r': - *sbuf++ = '\r'; - break; - case 't': - *sbuf++ = '\t'; - break; - case 'x': - /* Grab a hex code */ - c[1] = '\0'; - for (i = 0; i < 2; i++) { - if (isxdigit((unsigned char)*str) == 0) - break; - c[i] = *str++; - } - if (c[1] != '\0') { - c[2] = '\0'; - *sbuf++ = strtol(c, NULL, 16); - } else - l--; - break; - case '0': - /* Grab an octal code */ - c[2] = '\0'; - for (i = 0; i < 3; i++) { - if (*str < '0' || *str > '7') - break; - c[i] = *str++; - } - if (c[2] != '\0') { - i = strtol(c, NULL, 8); - if (i > 255) - i = 255; - *sbuf ++= i; - } else - l--; - break; - default: - *sbuf++ = *str++; - } - } else - *sbuf++ = *str++; + if (a == NULL) + syslog(lvl, "%s: %s %s %s", + iface->name, msg, tfrom, inet_ntoa(addr)); + else + syslog(lvl, "%s: %s %s %s %s", + iface->name, msg, a, tfrom, inet_ntoa(addr)); } - return l; + free(a); } static int -parse_option(int opt, char *oarg, struct options *options) +blacklisted_ip(const struct if_options *ifo, in_addr_t addr) { - int i; - char *p; - ssize_t s; + size_t i; + + for (i = 0; i < ifo->blacklist_len; i += 2) + if (ifo->blacklist[i] == (addr & ifo->blacklist[i + 1])) + return 1; + return 0; +} + +static int +whitelisted_ip(const struct if_options *ifo, in_addr_t addr) +{ + size_t i; + + if (ifo->whitelist_len == 0) + return -1; + for (i = 0; i < ifo->whitelist_len; i += 2) + if (ifo->whitelist[i] == (addr & ifo->whitelist[i + 1])) + return 1; + return 0; +} + +static void +handle_dhcp(struct interface *iface, struct dhcp_message **dhcpp, const struct in_addr *from) +{ + struct if_state *state = iface->state; + struct if_options *ifo = state->options; + struct dhcp_message *dhcp = *dhcpp; + struct dhcp_lease *lease = &state->lease; + uint8_t type, tmp; struct in_addr addr; + size_t i; - switch(opt) { - case 'b': - options->options |= DHCPCD_BACKGROUND; - break; - case 'c': - strlcpy(options->script, oarg, sizeof(options->script)); - break; - case 'h': - if (oarg) - s = parse_string(options->hostname, - HOSTNAME_MAX_LEN, oarg); - else - s = 0; - if (s == -1) { - logger(LOG_ERR, "hostname: %s", strerror(errno)); - return -1; - } - if (s != 0 && options->hostname[0] == '.') { - logger(LOG_ERR, "hostname cannot begin with a ."); - return -1; - } - options->hostname[s] = '\0'; - break; - case 'i': - if (oarg) - s = parse_string((char *)options->vendorclassid + 1, - VENDORCLASSID_MAX_LEN, oarg); - else - s = 0; - if (s == -1) { - logger(LOG_ERR, "vendorclassid: %s", strerror(errno)); - return -1; + /* reset the message counter */ + state->interval = 0; + + /* We may have found a BOOTP server */ + if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1) + type = 0; + + if (type == DHCP_NAK) { + /* For NAK, only check if we require the ServerID */ + if (has_option_mask(ifo->requiremask, DHO_SERVERID) && + get_option_addr(&addr, dhcp, DHO_SERVERID) == -1) + { + log_dhcp(LOG_WARNING, "reject NAK", iface, dhcp, from); + return; } - *options->vendorclassid = (uint8_t)s; - break; - case 'l': - if (*oarg == '-') { - logger(LOG_ERR, - "leasetime must be a positive value"); - return -1; + /* We should restart on a NAK */ + log_dhcp(LOG_WARNING, "NAK:", iface, dhcp, from); + if (!(options & DHCPCD_TEST)) { + drop_config(iface, "NAK"); + unlink(iface->leasefile); } - errno = 0; - options->leasetime = (uint32_t)strtol(oarg, NULL, 0); - if (errno == EINVAL || errno == ERANGE) { - logger(LOG_ERR, "`%s' out of range", oarg); - return -1; + close_sockets(iface); + /* If we constantly get NAKS then we should slowly back off */ + add_timeout_sec(state->nakoff, start_interface, iface); + state->nakoff *= 2; + if (state->nakoff > NAKOFF_MAX) + state->nakoff = NAKOFF_MAX; + return; + } + + /* Ensure that all required options are present */ + for (i = 1; i < 255; i++) { + if (has_option_mask(ifo->requiremask, i) && + get_option_uint8(&tmp, dhcp, i) != 0) + { + /* If we are bootp, then ignore the need for serverid. + * To ignore bootp, require dhcp_message_type instead. */ + if (type == 0 && i == DHO_SERVERID) + continue; + log_dhcp(LOG_WARNING, "reject DHCP", iface, dhcp, from); + return; } - break; - case 'm': - options->metric = atoint(oarg); - if (options->metric < 0) { - logger(LOG_ERR, "metric must be a positive value"); - return -1; + } + + /* No NAK, so reset the backoff */ + state->nakoff = 1; + + if ((type == 0 || type == DHCP_OFFER) && + state->state == DHS_DISCOVER) + { + lease->frominfo = 0; + lease->addr.s_addr = dhcp->yiaddr; + lease->cookie = dhcp->cookie; + if (type == 0 || + get_option_addr(&lease->server, dhcp, DHO_SERVERID) != 0) + lease->server.s_addr = INADDR_ANY; + log_dhcp(LOG_INFO, "offered", iface, dhcp, from); + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + if (options & DHCPCD_TEST) { + free(state->old); + state->old = state->new; + state->new = state->offer; + state->offer = NULL; + state->reason = "TEST"; + run_script(iface); + exit(EXIT_SUCCESS); } - break; - case 'o': - if (make_option_mask(options->requestmask, &oarg, 1) != 0) { - logger(LOG_ERR, "unknown option `%s'", oarg); - return -1; + delete_timeout(send_discover, iface); + /* We don't request BOOTP addresses */ + if (type) { + /* We used to ARP check here, but that seems to be in + * violation of RFC2131 where it only describes + * DECLINE after REQUEST. + * It also seems that some MS DHCP servers actually + * ignore DECLINE if no REQUEST, ie we decline a + * DISCOVER. */ + start_request(iface); + return; } - break; - case 'p': - options->options |= DHCPCD_PERSISTENT; - break; - case 'q': - setloglevel(LOG_WARNING); - break; - case 's': - options->options |= DHCPCD_INFORM; - options->options |= DHCPCD_PERSISTENT; - options->options &= ~DHCPCD_ARP; - if (!oarg || *oarg == '\0') { - options->request_address.s_addr = 0; - break; - } else { - if ((p = strchr(oarg, '/'))) { - /* nullify the slash, so the -r option - * can read the address */ - *p++ = '\0'; - if (sscanf(p, "%d", &i) != 1 || - inet_cidrtoaddr(i, &options->request_netmask) != 0) - { - logger(LOG_ERR, - "`%s' is not a valid CIDR", - p); - return -1; - } - } + } + + if (type) { + if (type == DHCP_OFFER) { + log_dhcp(LOG_INFO, "ignoring offer of", + iface, dhcp, from); + return; } - /* FALLTHROUGH */ - case 'r': - if (!(options->options & DHCPCD_INFORM)) - options->options |= DHCPCD_REQUEST; - if (oarg && !inet_aton(oarg, &options->request_address)) { - logger(LOG_ERR, "`%s' is not a valid IP address", - oarg); - return -1; + + /* We should only be dealing with acks */ + if (type != DHCP_ACK) { + log_dhcp(LOG_ERR, "not ACK or OFFER", + iface, dhcp, from); + return; } - break; - case 't': - options->timeout = atoint(oarg); - if (options->timeout < 0) { - logger (LOG_ERR, "timeout must be a positive value"); - return -1; + + if (!(ifo->options & DHCPCD_INFORM)) + log_dhcp(LOG_INFO, "acknowledged", iface, dhcp, from); + } + + /* BOOTP could have already assigned this above, so check we still + * have a pointer. */ + if (*dhcpp) { + free(state->offer); + state->offer = dhcp; + *dhcpp = NULL; + } + + lease->frominfo = 0; + delete_timeout(NULL, iface); + + /* We now have an offer, so close the DHCP sockets. + * This allows us to safely ARP when broken DHCP servers send an ACK + * follows by an invalid NAK. */ + close_sockets(iface); + + if (ifo->options & DHCPCD_ARP && + iface->addr.s_addr != state->offer->yiaddr) + { + /* If the interface already has the address configured + * then we can't ARP for duplicate detection. */ + addr.s_addr = state->offer->yiaddr; + if (has_address(iface->name, &addr, NULL) != 1) { + state->claims = 0; + state->probes = 0; + state->conflicts = 0; + state->state = DHS_PROBE; + send_arp_probe(iface); + return; } - break; - case 'u': - s = USERCLASS_MAX_LEN - options->userclass[0] - 1; - s = parse_string((char *)options->userclass + options->userclass[0] + 2, - s, oarg); - if (s == -1) { - logger(LOG_ERR, "userclass: %s", strerror(errno)); - return -1; + } + + bind_interface(iface); +} + +static void +handle_dhcp_packet(void *arg) +{ + struct interface *iface = arg; + uint8_t *packet; + struct dhcp_message *dhcp = NULL; + const uint8_t *pp; + ssize_t bytes; + struct in_addr from; + int i; + + /* We loop through until our buffer is empty. + * The benefit is that if we get >1 DHCP packet in our buffer and + * the first one fails for any reason, we can use the next. */ + packet = xmalloc(udp_dhcp_len); + for(;;) { + bytes = get_raw_packet(iface, ETHERTYPE_IP, + packet, udp_dhcp_len); + if (bytes == 0 || bytes == -1) + break; + if (valid_udp_packet(packet, bytes, &from) == -1) { + syslog(LOG_ERR, "%s: invalid UDP packet from %s", + iface->name, inet_ntoa(from)); + continue; } - if (s != 0) { - options->userclass[options->userclass[0] + 1] = s; - options->userclass[0] += s + 1; + i = whitelisted_ip(iface->state->options, from.s_addr); + if (i == 0) { + syslog(LOG_WARNING, + "%s: non whitelisted DHCP packet from %s", + iface->name, inet_ntoa(from)); + continue; + } else if (i != 1 && + blacklisted_ip(iface->state->options, from.s_addr) == 1) + { + syslog(LOG_WARNING, + "%s: blacklisted DHCP packet from %s", + iface->name, inet_ntoa(from)); + continue; } - break; - case 'v': - p = strchr(oarg, ','); - if (!p || !p[1]) { - logger(LOG_ERR, "invalid vendor format"); - return -1; + if (iface->flags & IFF_POINTOPOINT && + iface->dst.s_addr != from.s_addr) + { + syslog(LOG_WARNING, + "%s: server %s is not destination", + iface->name, inet_ntoa(from)); } - *p = '\0'; - i = atoint(oarg); - oarg = p + 1; - if (i < 1 || i > 254) { - logger(LOG_ERR, "vendor option should be between" - " 1 and 254 inclusive"); - return -1; + bytes = get_udp_data(&pp, packet); + if ((size_t)bytes > sizeof(*dhcp)) { + syslog(LOG_ERR, + "%s: packet greater than DHCP size from %s", + iface->name, inet_ntoa(from)); + continue; } - s = VENDOR_MAX_LEN - options->vendor[0] - 2; - if (inet_aton(oarg, &addr) == 1) { - if (s < 6) { - s = -1; - errno = ENOBUFS; - } else - memcpy(options->vendor + options->vendor[0] + 3, - &addr.s_addr, sizeof(addr.s_addr)); - } else { - s = parse_string((char *)options->vendor + options->vendor[0] + 3, - s, oarg); + if (!dhcp) + dhcp = xzalloc(sizeof(*dhcp)); + memcpy(dhcp, pp, bytes); + if (dhcp->cookie != htonl(MAGIC_COOKIE)) { + syslog(LOG_DEBUG, "%s: bogus cookie from %s", + iface->name, inet_ntoa(from)); + continue; } - if (s == -1) { - logger(LOG_ERR, "vendor: %s", strerror(errno)); - return -1; + /* Ensure it's the right transaction */ + if (iface->state->xid != dhcp->xid) { + syslog(LOG_DEBUG, + "%s: wrong xid 0x%x (expecting 0x%x) from %s", + iface->name, dhcp->xid, iface->state->xid, + inet_ntoa(from)); + continue; } - if (s != 0) { - options->vendor[options->vendor[0] + 1] = i; - options->vendor[options->vendor[0] + 2] = s; - options->vendor[0] += s + 2; + /* Ensure packet is for us */ + if (iface->hwlen <= sizeof(dhcp->chaddr) && + memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen)) + { + syslog(LOG_DEBUG, "%s: xid 0x%x is not for hwaddr %s", + iface->name, dhcp->xid, + hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr))); + continue; } - break; - case 'A': - options->options &= ~DHCPCD_ARP; - /* IPv4LL requires ARP */ - options->options &= ~DHCPCD_IPV4LL; - break; - case 'B': - options->options &= ~DHCPCD_DAEMONISE; - break; - case 'C': - /* Commas to spaces for shell */ - while ((p = strchr(oarg, ','))) - *p = ' '; - s = strlen("skip_hooks=") + strlen(oarg) + 1; - p = xmalloc(sizeof(char) * s); - snprintf(p, s, "skip_hooks=%s", oarg); - add_environ(options, p, 0); - free(p); - break; - case 'D': - options->options |= DHCPCD_DUID; - break; - case 'E': - options->options |= DHCPCD_LASTLEASE; - break; - case 'F': - if (!oarg) { - options->fqdn = FQDN_BOTH; + handle_dhcp(iface, &dhcp, &from); + if (iface->raw_fd == -1) break; - } - if (strcmp(oarg, "none") == 0) - options->fqdn = FQDN_NONE; - else if (strcmp(oarg, "ptr") == 0) - options->fqdn = FQDN_PTR; - else if (strcmp(oarg, "both") == 0) - options->fqdn = FQDN_BOTH; - else if (strcmp(oarg, "disable") == 0) - options->fqdn = FQDN_DISABLE; - else { - logger(LOG_ERR, "invalid value `%s' for FQDN", - oarg); - return -1; - } - break; - case 'G': - options->options &= ~DHCPCD_GATEWAY; + } + free(packet); + free(dhcp); +} + +static void +send_release(struct interface *iface) +{ + struct timespec ts; + + if (iface->state->new != NULL && + iface->state->new->cookie == htonl(MAGIC_COOKIE)) + { + syslog(LOG_INFO, "%s: releasing lease of %s", + iface->name, inet_ntoa(iface->state->lease.addr)); + iface->state->xid = dhcp_xid(iface); + send_message(iface, DHCP_RELEASE, NULL); + /* Give the packet a chance to go before dropping the ip */ + ts.tv_sec = RELEASE_DELAY_S; + ts.tv_nsec = RELEASE_DELAY_NS; + nanosleep(&ts, NULL); + drop_config(iface, "RELEASE"); + } + unlink(iface->leasefile); +} + +void +send_decline(struct interface *iface) +{ + send_message(iface, DHCP_DECLINE, NULL); +} + +static void +configure_interface1(struct interface *iface) +{ + struct if_state *ifs = iface->state; + struct if_options *ifo = ifs->options; + uint8_t *duid; + size_t len = 0, ifl; + + /* Do any platform specific configuration */ + if_conf(iface); + + if (iface->flags & IFF_POINTOPOINT && !(ifo->options & DHCPCD_INFORM)) + ifo->options |= DHCPCD_STATIC; + if (iface->flags & IFF_NOARP || + ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) + ifo->options &= ~(DHCPCD_ARP | DHCPCD_IPV4LL); + if (ifo->options & DHCPCD_LINK && carrier_status(iface) == -1) + ifo->options &= ~DHCPCD_LINK; + + if (ifo->metric != -1) + iface->metric = ifo->metric; + + /* If we haven't specified a ClientID and our hardware address + * length is greater than DHCP_CHADDR_LEN then we enforce a ClientID + * of the hardware address family and the hardware address. */ + if (iface->hwlen > DHCP_CHADDR_LEN) + ifo->options |= DHCPCD_CLIENTID; + + /* Firewire and InfiniBand interfaces require ClientID and + * the broadcast option being set. */ + switch (iface->family) { + case ARPHRD_IEEE1394: /* FALLTHROUGH */ + case ARPHRD_INFINIBAND: + ifo->options |= DHCPCD_CLIENTID | DHCPCD_BROADCAST; break; - case 'I': - /* Strings have a type of 0 */; - options->clientid[1] = 0; - if (oarg) - s = parse_string_hwaddr((char *)options->clientid + 1, - CLIENTID_MAX_LEN, oarg, 1); - else - s = 0; - if (s == -1) { - logger(LOG_ERR, "clientid: %s", strerror(errno)); - return -1; - } - options->clientid[0] = (uint8_t)s; -#ifdef CMDLINE_COMPAT - if (s == 0) { - options->options &= ~DHCPCD_DUID; - options->options &= ~DHCPCD_CLIENTID; + } + + free(iface->clientid); + iface->clientid = NULL; + if (*ifo->clientid) { + iface->clientid = xmalloc(ifo->clientid[0] + 1); + memcpy(iface->clientid, ifo->clientid, ifo->clientid[0] + 1); + } else if (ifo->options & DHCPCD_CLIENTID) { + if (ifo->options & DHCPCD_DUID) { + duid = xmalloc(DUID_LEN); + if ((len = get_duid(duid, iface)) == 0) + syslog(LOG_ERR, "get_duid: %m"); } -#else - options->options |= DHCPCD_CLIENTID; -#endif - break; - case 'K': - options->options &= ~DHCPCD_LINK; - break; - case 'L': - options->options &= ~DHCPCD_IPV4LL; - break; - case 'O': - if (make_option_mask(options->requestmask, &oarg, -1) != 0 || - make_option_mask(options->requiremask, &oarg, -1) != 0 || - make_option_mask(options->nomask, &oarg, 1) != 0) - { - logger(LOG_ERR, "unknown option `%s'", oarg); - return -1; + if (len > 0) { + iface->clientid = xmalloc(len + 6); + iface->clientid[0] = len + 5; + iface->clientid[1] = 255; /* RFC 4361 */ + ifl = strlen(iface->name); + if (ifl < 5) { + memcpy(iface->clientid + 2, iface->name, ifl); + if (ifl < 4) + memset(iface->clientid + 2 + ifl, + 0, 4 - ifl); + } else { + ifl = htonl(if_nametoindex(iface->name)); + memcpy(iface->clientid + 2, &ifl, 4); + } + } else if (len == 0) { + len = iface->hwlen + 1; + iface->clientid = xmalloc(len + 1); + iface->clientid[0] = len; + iface->clientid[1] = iface->family; + memcpy(iface->clientid + 2, iface->hwaddr, + iface->hwlen); } - break; - case 'Q': - if (make_option_mask(options->requiremask, &oarg, 1) != 0 || - make_option_mask(options->requestmask, &oarg, 1) != 0) - { - logger(LOG_ERR, "unknown option `%s'", oarg); - return -1; + } + if (ifo->options & DHCPCD_CLIENTID) + syslog(LOG_DEBUG, "%s: using ClientID %s", iface->name, + hwaddr_ntoa(iface->clientid + 1, *iface->clientid)); + else + syslog(LOG_DEBUG, "%s: using hwaddr %s", iface->name, + hwaddr_ntoa(iface->hwaddr, iface->hwlen)); +} + +int +select_profile(struct interface *iface, const char *profile) +{ + struct if_options *ifo; + int ret; + + ret = 0; + ifo = read_config(cffile, iface->name, iface->ssid, profile); + if (ifo == NULL) { + syslog(LOG_DEBUG, "%s: no profile %s", iface->name, profile); + ret = -1; + goto exit; + } + if (profile != NULL) { + strlcpy(iface->state->profile, profile, + sizeof(iface->state->profile)); + syslog(LOG_INFO, "%s: selected profile %s", + iface->name, profile); + } else + *iface->state->profile = '\0'; + free_options(iface->state->options); + iface->state->options = ifo; + +exit: + if (profile) + configure_interface1(iface); + return ret; +} + +static void +start_fallback(void *arg) +{ + struct interface *iface; + + iface = (struct interface *)arg; + select_profile(iface, iface->state->options->fallback); + start_interface(iface); +} + +static void +configure_interface(struct interface *iface, int argc, char **argv) +{ + select_profile(iface, NULL); + add_options(iface->state->options, argc, argv); + configure_interface1(iface); +} + +static void +handle_carrier(const char *ifname) +{ + struct interface *iface; + int carrier; + + if (!(options & DHCPCD_LINK)) + return; + for (iface = ifaces; iface; iface = iface->next) + if (strcmp(iface->name, ifname) == 0) + break; + if (!iface || !(iface->state->options->options & DHCPCD_LINK)) + return; + carrier = carrier_status(iface); + if (carrier == -1) + syslog(LOG_ERR, "%s: carrier_status: %m", ifname); + else if (carrier == 0 || !(iface->flags & IFF_RUNNING)) { + if (iface->carrier != LINK_DOWN) { + iface->carrier = LINK_DOWN; + syslog(LOG_INFO, "%s: carrier lost", iface->name); + close_sockets(iface); + delete_timeouts(iface, start_expire, NULL); + drop_config(iface, "NOCARRIER"); } - break; - case 'X': - if (!inet_aton(oarg, &addr)) { - logger(LOG_ERR, "`%s' is not a valid IP address", - oarg); - return -1; + } else if (carrier == 1 && (iface->flags & IFF_RUNNING)) { + if (iface->carrier != LINK_UP) { + iface->carrier = LINK_UP; + syslog(LOG_INFO, "%s: carrier acquired", iface->name); + if (iface->wireless) + getifssid(iface->name, iface->ssid); + configure_interface(iface, margc, margv); + iface->state->interval = 0; + iface->state->reason = "CARRIER"; + run_script(iface); + start_interface(iface); } - options->blacklist = xrealloc(options->blacklist, - sizeof(in_addr_t) * (options->blacklist_len + 1)); - options->blacklist[options->blacklist_len] = addr.s_addr; - options->blacklist_len++; - break; - default: - return 0; } +} - return 1; +void +start_discover(void *arg) +{ + struct interface *iface = arg; + struct if_options *ifo = iface->state->options; + + iface->state->state = DHS_DISCOVER; + iface->state->xid = dhcp_xid(iface); + delete_timeout(NULL, iface); + if (ifo->fallback) + add_timeout_sec(ifo->timeout, start_fallback, iface); + else if (ifo->options & DHCPCD_IPV4LL && + !IN_LINKLOCAL(htonl(iface->addr.s_addr))) + { + if (IN_LINKLOCAL(htonl(iface->state->fail.s_addr))) + add_timeout_sec(RATE_LIMIT_INTERVAL, start_ipv4ll, iface); + else + add_timeout_sec(ifo->timeout, start_ipv4ll, iface); + } + syslog(LOG_INFO, "%s: broadcasting for a lease", iface->name); + send_discover(iface); } -static int -parse_config_line(const char *opt, char *line, struct options *options) +void +start_request(void *arg) { - unsigned int i; + struct interface *iface = arg; - for (i = 0; i < sizeof(longopts) / sizeof(longopts[0]); i++) { - if (!longopts[i].name || - strcmp(longopts[i].name, opt) != 0) - continue; + iface->state->state = DHS_REQUEST; + send_request(iface); +} - if (longopts[i].has_arg == required_argument && !line) { - fprintf(stderr, - PACKAGE ": option requires an argument -- %s\n", - opt); - return -1; - } +void +start_renew(void *arg) +{ + struct interface *iface = arg; + + syslog(LOG_INFO, "%s: renewing lease of %s", + iface->name, inet_ntoa(iface->state->lease.addr)); + iface->state->state = DHS_RENEW; + iface->state->xid = dhcp_xid(iface); + send_renew(iface); +} + +void +start_rebind(void *arg) +{ + struct interface *iface = arg; + + syslog(LOG_ERR, "%s: failed to renew, attempting to rebind", + iface->name); + iface->state->state = DHS_REBIND; + delete_timeout(send_renew, iface); + iface->state->lease.server.s_addr = 0; + send_rebind(iface); +} + +static void +start_timeout(void *arg) +{ + struct interface *iface = arg; + + bind_interface(iface); + iface->state->interval = 0; + start_discover(iface); +} + +static struct dhcp_message * +dhcp_message_new(struct in_addr *addr, struct in_addr *mask) +{ + struct dhcp_message *dhcp; + uint8_t *p; + + dhcp = xzalloc(sizeof(*dhcp)); + dhcp->yiaddr = addr->s_addr; + p = dhcp->options; + if (mask && mask->s_addr != INADDR_ANY) { + *p++ = DHO_SUBNETMASK; + *p++ = sizeof(mask->s_addr); + memcpy(p, &mask->s_addr, sizeof(mask->s_addr)); + p+= sizeof(mask->s_addr); + } + *p++ = DHO_END; + return dhcp; +} + +static int +handle_3rdparty(struct interface *iface) +{ + struct if_options *ifo; + struct in_addr addr, net, dst; + + ifo = iface->state->options; + if (ifo->req_addr.s_addr != INADDR_ANY) + return 0; - return parse_option(longopts[i].val, line, options); + if (get_address(iface->name, &addr, &net, &dst) == 1) + handle_ifa(RTM_NEWADDR, iface->name, &addr, &net, &dst); + else { + syslog(LOG_INFO, + "%s: waiting for 3rd party to configure IP address", + iface->name); + iface->state->reason = "3RDPARTY"; + run_script(iface); } + return 1; +} - fprintf(stderr, PACKAGE ": unknown option -- %s\n", opt); - return -1; +static void +start_static(struct interface *iface) +{ + struct if_options *ifo; + + if (handle_3rdparty(iface)) + return; + ifo = iface->state->options; + iface->state->offer = + dhcp_message_new(&ifo->req_addr, &ifo->req_mask); + delete_timeout(NULL, iface); + bind_interface(iface); } -#ifdef ANDROID -void switchUser() { - gid_t groups[] = { AID_INET, AID_SHELL }; - setgroups(sizeof(groups)/sizeof(groups[0]), groups); - - prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); - - setgid(AID_DHCP); - setuid(AID_DHCP); - - struct __user_cap_header_struct header; - struct __user_cap_data_struct cap; - header.version = _LINUX_CAPABILITY_VERSION; - header.pid = 0; - cap.effective = cap.permitted = - (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | - (1 << CAP_NET_BROADCAST) | (1 << CAP_NET_BIND_SERVICE); - cap.inheritable = 0; - capset(&header, &cap); +static void +start_inform(struct interface *iface) +{ + if (handle_3rdparty(iface)) + return; + + if (options & DHCPCD_TEST) { + iface->addr.s_addr = iface->state->options->req_addr.s_addr; + iface->net.s_addr = iface->state->options->req_mask.s_addr; + } else { + iface->state->options->options |= DHCPCD_STATIC; + start_static(iface); + } + + iface->state->state = DHS_INFORM; + iface->state->xid = dhcp_xid(iface); + send_inform(iface); } -#endif /* ANDROID */ -int -main(int argc, char **argv) +void +start_reboot(struct interface *iface) { - struct options *options; - int opt; - int option_index = 0; - char *prefix; - pid_t pid; - int debug = 0; - int i, r; - int pid_fd = -1; - int sig = 0; - int retval = EXIT_FAILURE; - char *line, *option, *p, *buffer = NULL; - size_t len = 0; - FILE *f; - char *cf = NULL; - char *intf = NULL; - struct timespec ts; + struct if_options *ifo = iface->state->options; -#ifdef ANDROID - switchUser(); -#endif - closefrom(3); - /* Saves calling fflush(stream) in the logger */ - setlinebuf(stdout); - openlog(PACKAGE, LOG_PID, LOG_DAEMON); - setlogprefix(PACKAGE ": "); - - options = xzalloc(sizeof(*options)); - options->options |= DHCPCD_GATEWAY | DHCPCD_DAEMONISE; - options->options |= DHCPCD_ARP | DHCPCD_IPV4LL | DHCPCD_LINK; - options->timeout = DEFAULT_TIMEOUT; - strlcpy(options->script, SCRIPT, sizeof(options->script)); - - options->vendorclassid[0] = snprintf((char *)options->vendorclassid + 1, - VENDORCLASSID_MAX_LEN, - "%s %s", PACKAGE, VERSION); - -#ifdef CMDLINE_COMPAT - options->options |= DHCPCD_CLIENTID; - add_option_mask(options->requestmask, DHO_DNSSERVER); - add_option_mask(options->requestmask, DHO_DNSDOMAIN); - add_option_mask(options->requestmask, DHO_DNSSEARCH); - add_option_mask(options->requestmask, DHO_NISSERVER); - add_option_mask(options->requestmask, DHO_NISDOMAIN); - add_option_mask(options->requestmask, DHO_NTPSERVER); - - /* If the duid file exists, then enable duid by default - * This means we don't break existing clients that easily :) */ - if ((f = fopen(DUID, "r"))) { - options->options |= DHCPCD_DUID; - fclose(f); + if (ifo->options & DHCPCD_LINK && iface->carrier == LINK_DOWN) { + syslog(LOG_INFO, "%s: waiting for carrier", iface->name); + return; } -#endif + if (ifo->options & DHCPCD_STATIC) { + start_static(iface); + return; + } + if (ifo->reboot == 0 || iface->state->offer == NULL) { + start_discover(iface); + return; + } + if (ifo->options & DHCPCD_INFORM) { + syslog(LOG_INFO, "%s: informing address of %s", + iface->name, inet_ntoa(iface->state->lease.addr)); + } else if (iface->state->offer->cookie == 0) { + if (ifo->options & DHCPCD_IPV4LL) { + iface->state->claims = 0; + send_arp_announce(iface); + } else + start_discover(iface); + return; + } else { + syslog(LOG_INFO, "%s: rebinding lease of %s", + iface->name, inet_ntoa(iface->state->lease.addr)); + } + iface->state->state = DHS_REBOOT; + iface->state->xid = dhcp_xid(iface); + iface->state->lease.server.s_addr = 0; + delete_timeout(NULL, iface); + if (ifo->fallback) + add_timeout_sec(ifo->reboot, start_fallback, iface); + else if (ifo->options & DHCPCD_LASTLEASE && + iface->state->lease.frominfo) + add_timeout_sec(ifo->reboot, start_timeout, iface); + else if (!(ifo->options & DHCPCD_INFORM && + options & (DHCPCD_MASTER | DHCPCD_DAEMONISED))) + add_timeout_sec(ifo->reboot, start_expire, iface); + /* Don't bother ARP checking as the server could NAK us first. */ + if (ifo->options & DHCPCD_INFORM) + send_inform(iface); + else + send_request(iface); +} - gethostname(options->hostname, HOSTNAME_MAX_LEN); - /* Ensure that the hostname is NULL terminated */ - options->hostname[HOSTNAME_MAX_LEN] = '\0'; - if (strcmp(options->hostname, "(none)") == 0 || - strcmp(options->hostname, "localhost") == 0) - options->hostname[0] = '\0'; +void +start_interface(void *arg) +{ + struct interface *iface = arg; + struct if_options *ifo = iface->state->options; + struct stat st; + struct timeval now; + uint32_t l; + int nolease; + + handle_carrier(iface->name); + if (iface->carrier == LINK_DOWN) { + syslog(LOG_INFO, "%s: waiting for carrier", iface->name); + return; + } - while ((opt = getopt_long(argc, argv, OPTS EXTRA_OPTS, - longopts, &option_index)) != -1) - { - switch (opt) { + iface->start_uptime = uptime(); + free(iface->state->offer); + iface->state->offer = NULL; + + if (iface->state->arping_index < ifo->arping_len) { + start_arping(iface); + return; + } + if (ifo->options & DHCPCD_STATIC) { + start_static(iface); + return; + } + if (ifo->options & DHCPCD_INFORM) { + start_inform(iface); + return; + } + if (iface->hwlen == 0 && ifo->clientid[0] == '\0') { + syslog(LOG_WARNING, "%s: needs a clientid to configure", + iface->name); + drop_config(iface, "FAIL"); + close_sockets(iface); + delete_timeout(NULL, iface); + return; + } + /* We don't want to read the old lease if we NAK an old test */ + nolease = iface->state->offer && options & DHCPCD_TEST; + if (!nolease) + iface->state->offer = read_lease(iface); + if (iface->state->offer) { + get_lease(&iface->state->lease, iface->state->offer); + iface->state->lease.frominfo = 1; + if (iface->state->offer->cookie == 0) { + if (iface->state->offer->yiaddr == + iface->addr.s_addr) + { + free(iface->state->offer); + iface->state->offer = NULL; + } + } else if (iface->state->lease.leasetime != ~0U && + stat(iface->leasefile, &st) == 0) + { + /* Offset lease times and check expiry */ + gettimeofday(&now, NULL); + if ((time_t)iface->state->lease.leasetime < + now.tv_sec - st.st_mtime) + { + syslog(LOG_DEBUG, + "%s: discarding expired lease", + iface->name); + free(iface->state->offer); + iface->state->offer = NULL; + iface->state->lease.addr.s_addr = 0; + } else { + l = now.tv_sec - st.st_mtime; + iface->state->lease.leasetime -= l; + iface->state->lease.renewaltime -= l; + iface->state->lease.rebindtime -= l; + } + } + } + if (iface->state->offer == NULL) + start_discover(iface); + else if (iface->state->offer->cookie == 0 && + iface->state->options->options & DHCPCD_IPV4LL) + start_ipv4ll(iface); + else + start_reboot(iface); +} + +static void +init_state(struct interface *iface, int argc, char **argv) +{ + struct if_state *ifs; + + if (iface->state) + ifs = iface->state; + else + ifs = iface->state = xzalloc(sizeof(*ifs)); + + ifs->state = DHS_INIT; + ifs->reason = "PREINIT"; + ifs->nakoff = 1; + configure_interface(iface, argc, argv); + if (!(options & DHCPCD_TEST)) + run_script(iface); + /* We need to drop the leasefile so that start_interface + * doesn't load it. */ + if (ifs->options->options & DHCPCD_REQUEST) + unlink(iface->leasefile); + + if (ifs->options->options & DHCPCD_LINK) { + switch (carrier_status(iface)) { case 0: - if (longopts[option_index].flag) - break; - logger(LOG_ERR, "option `%s' should set a flag", - longopts[option_index].name); - goto abort; - case 'f': - cf = optarg; + iface->carrier = LINK_DOWN; + ifs->reason = "NOCARRIER"; break; - case 'V': - print_options(); - goto abort; - case '?': - usage(); - goto abort; + case 1: + iface->carrier = LINK_UP; + ifs->reason = "CARRIER"; + break; + default: + iface->carrier = LINK_UNKNOWN; + return; } + if (!(options & DHCPCD_TEST)) + run_script(iface); + } else + iface->carrier = LINK_UNKNOWN; +} + +void +handle_interface(int action, const char *ifname) +{ + struct interface *ifs, *ifp, *ifn, *ifl = NULL; + const char * const argv[] = { ifname }; + int i; + + if (action == -1) { + ifp = find_interface(ifname); + if (ifp != NULL) + stop_interface(ifp); + return; + } else if (action == 0) { + handle_carrier(ifname); + return; } - if (doversion) - printf(""PACKAGE" "VERSION"\n%s\n", copyright); + /* If running off an interface list, check it's in it. */ + if (ifc) { + for (i = 0; i < ifc; i++) + if (strcmp(ifv[i], ifname) == 0) + break; + if (i >= ifc) + return; + } - if (dohelp) - usage(); + ifs = discover_interfaces(-1, UNCONST(argv)); + for (ifp = ifs; ifp; ifp = ifp->next) { + if (strcmp(ifp->name, ifname) != 0) + continue; + /* Check if we already have the interface */ + for (ifn = ifaces; ifn; ifn = ifn->next) { + if (strcmp(ifn->name, ifp->name) == 0) + break; + ifl = ifn; + } + if (ifn) { + /* The flags and hwaddr could have changed */ + ifn->flags = ifp->flags; + ifn->hwlen = ifp->hwlen; + if (ifp->hwlen != 0) + memcpy(ifn->hwaddr, ifp->hwaddr, ifn->hwlen); + } else { + if (ifl) + ifl->next = ifp; + else + ifaces = ifp; + } + init_state(ifp, 0, NULL); + start_interface(ifp); + } +} - if (optind < argc) { - if (strlen(argv[optind]) >= IF_NAMESIZE) { - logger(LOG_ERR, - "`%s' too long for an interface name (max=%d)", - argv[optind], IF_NAMESIZE); - goto abort; +#ifdef RTM_CHGADDR +void +handle_hwaddr(const char *ifname, unsigned char *hwaddr, size_t hwlen) +{ + struct interface *ifp; + struct if_options *ifo; + + for (ifp = ifaces; ifp; ifp = ifp->next) + if (strcmp(ifp->name, ifname) == 0 && ifp->hwlen <= hwlen) { + ifo = ifp->state->options; + if (!(ifo->options & + (DHCPCD_INFORM | DHCPCD_STATIC | DHCPCD_CLIENTID)) + && ifp->state->new != NULL && + ifp->state->new->cookie == htonl(MAGIC_COOKIE)) + { + syslog(LOG_INFO, + "%s: expiring for new hardware address", + ifp->name); + drop_config(ifp, "EXPIRE"); + } + memcpy(ifp->hwaddr, hwaddr, hwlen); + ifp->hwlen = hwlen; + if (!(ifo->options & + (DHCPCD_INFORM | DHCPCD_STATIC | DHCPCD_CLIENTID))) + { + syslog(LOG_DEBUG, "%s: using hwaddr %s", + ifp->name, + hwaddr_ntoa(ifp->hwaddr, ifp->hwlen)); + ifp->state->interval = 0; + ifp->state->nakoff = 1; + start_interface(ifp); + } } - strlcpy(options->interface, argv[optind], - sizeof(options->interface)); - } else { - /* If only version was requested then exit now */ - if (doversion || dohelp) { - retval = 0; - goto abort; + free(hwaddr); +} +#endif + +void +handle_ifa(int type, const char *ifname, + struct in_addr *addr, struct in_addr *net, struct in_addr *dst) +{ + struct interface *ifp; + struct if_options *ifo; + int i; + + if (addr->s_addr == INADDR_ANY) + return; + for (ifp = ifaces; ifp; ifp = ifp->next) + if (strcmp(ifp->name, ifname) == 0) + break; + if (ifp == NULL) + return; + ifo = ifp->state->options; + if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) == 0 || + ifo->req_addr.s_addr != INADDR_ANY) + return; + + switch (type) { + case RTM_DELADDR: + if (ifp->state->new && + ifp->state->new->yiaddr == addr->s_addr) + drop_config(ifp, "EXPIRE"); + break; + case RTM_NEWADDR: + free(ifp->state->old); + ifp->state->old = ifp->state->new; + ifp->state->new = dhcp_message_new(addr, net); + ifp->dst.s_addr = dst ? dst->s_addr : INADDR_ANY; + if (dst) { + for (i = 1; i < 255; i++) + if (i != DHO_ROUTER && + has_option_mask(ifo->dstmask, i)) + dhcp_message_add_addr( + ifp->state->new, + i, *dst); + } + ifp->state->reason = "STATIC"; + build_routes(); + run_script(ifp); + if (ifo->options & DHCPCD_INFORM) { + ifp->state->state = DHS_INFORM; + ifp->state->xid = dhcp_xid(ifp); + ifp->state->lease.server.s_addr = + dst ? dst->s_addr : INADDR_ANY; + ifp->addr = *addr; + ifp->net = *net; + send_inform(ifp); } + break; + } +} + +/* ARGSUSED */ +static void +handle_link(_unused void *arg) +{ + if (manage_link(linkfd) == -1) + syslog(LOG_ERR, "manage_link: %m"); +} - logger(LOG_ERR, "no interface specified"); - goto abort; +static void +if_reboot(struct interface *iface, int argc, char **argv) +{ + const struct if_options *ifo; + int opt; + + ifo = iface->state->options; + opt = ifo->options; + configure_interface(iface, argc, argv); + ifo = iface->state->options; + iface->state->interval = 0; + if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC) && + iface->addr.s_addr != ifo->req_addr.s_addr) || + (opt & (DHCPCD_INFORM | DHCPCD_STATIC) && + !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)))) + { + drop_config(iface, "EXPIRE"); + } else { + free(iface->state->offer); + iface->state->offer = NULL; } + start_interface(iface); +} - /* Parse our options file */ - f = fopen(cf ? cf : CONFIG, "r"); - if (f) { - r = 1; - while ((get_line(&buffer, &len, f))) { - line = buffer; - while ((option = strsep(&line, " \t"))) - if (*option != '\0') +static void +reconf_reboot(int action, int argc, char **argv, int oi) +{ + struct interface *ifl, *ifn, *ifp, *ifs, *ift; + + ifs = discover_interfaces(argc - oi, argv + oi); + if (ifs == NULL) + return; + + /* Remove any old interfaces */ + if (ifaces) { + for (ifl = NULL; ifl != ifaces;) { + /* Work our way backwards */ + for (ifp = ifaces; ifp; ifp = ifp->next) + if (ifp->next == ifl) { + ifl = ifp; break; - if (!option || *option == '\0' || *option == '#') - continue; - /* Trim leading whitespace */ - if (line) { - while (*line != '\0' && (*line == ' ' || *line == '\t')) - line++; + } + for (ifn = ifs; ifn; ifn = ifn->next) + if (strcmp(ifn->name, ifp->name) == 0) + break; + if (ifn == NULL) { + ifl = ifp->next; + stop_interface(ifp); } - /* Trim trailing whitespace */ - if (line && *line) { - p = line + strlen(line) - 1; - while (p != line && - (*p == ' ' || *p == '\t') && - *(p - 1) != '\\') - *p-- = '\0'; + } + } + + for (ifp = ifs; ifp && (ift = ifp->next, 1); ifp = ift) { + ifl = NULL; + for (ifn = ifaces; ifn; ifn = ifn->next) { + if (strcmp(ifn->name, ifp->name) == 0) + break; + ifl = ifn; + } + if (ifn) { + if (action) + if_reboot(ifn, argc, argv); + else if (ifn->state->new) + configure(ifn); + free_interface(ifp); + } else { + ifp->next = NULL; + init_state(ifp, argc, argv); + start_interface(ifp); + if (ifl) + ifl->next = ifp; + else + ifaces = ifp; + } + } + + sort_interfaces(); +} + +/* ARGSUSED */ +static void +handle_signal(_unused void *arg) +{ + struct interface *ifp, *ifl; + struct if_options *ifo; + int sig = signal_read(); + int do_release, do_rebind, i; + + do_rebind = do_release = 0; + switch (sig) { + case SIGINT: + syslog(LOG_INFO, "received SIGINT, stopping"); + break; + case SIGTERM: + syslog(LOG_INFO, "received SIGTERM, stopping"); + break; + case SIGALRM: + syslog(LOG_INFO, "received SIGALRM, rebinding"); + for (i = 0; i < ifac; i++) + free(ifav[i]); + free(ifav); + ifav = NULL; + ifac = 0; + for (i = 0; i < ifdc; i++) + free(ifdv[i]); + free(ifdv); + ifdc = 0; + ifdv = NULL; + ifo = read_config(cffile, NULL, NULL, NULL); + add_options(ifo, margc, margv); + /* We need to preserve these two options. */ + if (options & DHCPCD_MASTER) + ifo->options |= DHCPCD_MASTER; + if (options & DHCPCD_DAEMONISED) + ifo->options |= DHCPCD_DAEMONISED; + options = ifo->options; + free_options(ifo); + reconf_reboot(1, 0, NULL, 0); + return; + case SIGHUP: + syslog(LOG_INFO, "received SIGHUP, releasing"); + do_release = 1; + break; + case SIGUSR1: + syslog(LOG_INFO, "received SIGUSR, reconfiguring"); + for (ifp = ifaces; ifp; ifp = ifp->next) + if (ifp->state->new) + configure(ifp); + return; + case SIGPIPE: + syslog(LOG_WARNING, "received SIGPIPE"); + return; + default: + syslog(LOG_ERR, + "received signal %d, but don't know what to do with it", + sig); + return; + } + + if (options & DHCPCD_TEST) + exit(EXIT_FAILURE); + + /* As drop_config could re-arrange the order, we do it like this. */ + for (;;) { + /* Be sane and drop the last config first */ + ifl = NULL; + for (ifp = ifaces; ifp; ifp = ifp->next) { + if (ifp->next == NULL) + break; + ifl = ifp; + } + if (ifp == NULL) + break; + if (ifp->carrier != LINK_DOWN && + (do_release || + ifp->state->options->options & DHCPCD_RELEASE)) + send_release(ifp); + stop_interface(ifp); + } + exit(EXIT_FAILURE); +} + +int +handle_args(struct fd_list *fd, int argc, char **argv) +{ + struct interface *ifp; + int do_exit = 0, do_release = 0, do_reboot = 0, do_reconf = 0; + int opt, oi = 0; + ssize_t len; + size_t l; + struct iovec iov[2]; + char *tmp, *p; + + if (fd != NULL) { + /* Special commands for our control socket */ + if (strcmp(*argv, "--version") == 0) { + len = strlen(VERSION) + 1; + iov[0].iov_base = &len; + iov[0].iov_len = sizeof(ssize_t); + iov[1].iov_base = UNCONST(VERSION); + iov[1].iov_len = len; + if (writev(fd->fd, iov, 2) == -1) { + syslog(LOG_ERR, "writev: %m"); + return -1; } - if (strcmp(option, "interface") == 0) { - free(intf); - intf = xstrdup(line); - continue; + return 0; + } else if (strcmp(*argv, "--getconfigfile") == 0) { + len = strlen(cffile ? cffile : CONFIG) + 1; + iov[0].iov_base = &len; + iov[0].iov_len = sizeof(ssize_t); + iov[1].iov_base = cffile ? cffile : UNCONST(CONFIG); + iov[1].iov_len = len; + if (writev(fd->fd, iov, 2) == -1) { + syslog(LOG_ERR, "writev: %m"); + return -1; + } + return 0; + } else if (strcmp(*argv, "--getinterfaces") == 0) { + len = 0; + if (argc == 1) { + for (ifp = ifaces; ifp; ifp = ifp->next) + len++; + len = write(fd->fd, &len, sizeof(len)); + if (len != sizeof(len)) + return -1; + for (ifp = ifaces; ifp; ifp = ifp->next) + send_interface(fd->fd, ifp); + return 0; } - /* If we're in an interface block don't use these - * options unless it's for us */ - if (intf && strcmp(intf, options->interface) != 0) + opt = 0; + while (argv[++opt] != NULL) { + for (ifp = ifaces; ifp; ifp = ifp->next) + if (strcmp(argv[opt], ifp->name) == 0) + len++; + } + len = write(fd->fd, &len, sizeof(len)); + if (len != sizeof(len)) + return -1; + opt = 0; + while (argv[++opt] != NULL) { + for (ifp = ifaces; ifp; ifp = ifp->next) + if (strcmp(argv[opt], ifp->name) == 0) + send_interface(fd->fd, ifp); + } + return 0; + } else if (strcmp(*argv, "--listen") == 0) { + fd->listener = 1; + return 0; + } + } + + /* Log the command */ + len = 0; + for (opt = 0; opt < argc; opt++) + len += strlen(argv[opt]) + 1; + tmp = p = xmalloc(len + 1); + for (opt = 0; opt < argc; opt++) { + l = strlen(argv[opt]); + strlcpy(p, argv[opt], l + 1); + p += l; + *p++ = ' '; + } + *--p = '\0'; + syslog(LOG_INFO, "control command: %s", tmp); + free(tmp); + + optind = 0; + while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) + { + switch (opt) { + case 'g': + do_reconf = 1; + break; + case 'k': + do_release = 1; + break; + case 'n': + do_reboot = 1; + break; + case 'x': + do_exit = 1; + break; + } + } + + /* We need at least one interface */ + if (optind == argc) { + syslog(LOG_ERR, "handle_args: no interface"); + return -1; + } + + if (do_release || do_exit) { + for (oi = optind; oi < argc; oi++) { + for (ifp = ifaces; ifp; ifp = ifp->next) + if (strcmp(ifp->name, argv[oi]) == 0) + break; + if (!ifp) continue; - r = parse_config_line(option, line, options); - if (r != 1) - break; + if (do_release) + ifp->state->options->options |= DHCPCD_RELEASE; + if (ifp->state->options->options & DHCPCD_RELEASE && + ifp->carrier != LINK_DOWN) + send_release(ifp); + stop_interface(ifp); } - free(buffer); - free(intf); - fclose(f); - if (r == 0) + return 0; + } + + reconf_reboot(do_reboot, argc, argv, optind); + return 0; +} + +void +open_sockets(struct interface *iface) +{ + if (iface->raw_fd == -1) { + if (open_socket(iface, ETHERTYPE_IP) == -1) + syslog(LOG_ERR, "%s: open_socket: %m", iface->name); + else + add_event(iface->raw_fd, handle_dhcp_packet, iface); + } + if (iface->udp_fd == -1 && + iface->addr.s_addr != 0 && + iface->state->new != NULL && + (iface->state->new->cookie == htonl(MAGIC_COOKIE) || + iface->state->options->options & DHCPCD_INFORM)) + { + if (open_udp_socket(iface) == -1 && errno != EADDRINUSE) + syslog(LOG_ERR, "%s: open_udp_socket: %m", iface->name); + } +} + +void +close_sockets(struct interface *iface) +{ + if (iface->arp_fd != -1) { + delete_event(iface->arp_fd); + close(iface->arp_fd); + iface->arp_fd = -1; + } + if (iface->raw_fd != -1) { + delete_event(iface->raw_fd); + close(iface->raw_fd); + iface->raw_fd = -1; + } + if (iface->udp_fd != -1) { + /* we don't listen to events on the udp */ + close(iface->udp_fd); + iface->udp_fd = -1; + } +} + +int +main(int argc, char **argv) +{ + struct interface *iface; + int opt, oi = 0, signal_fd, sig = 0, i, control_fd; + size_t len; + pid_t pid; + struct timespec ts; + + closefrom(3); + openlog(PACKAGE, LOG_PERROR | LOG_PID, LOG_DAEMON); + setlogmask(LOG_UPTO(LOG_INFO)); + + /* Test for --help and --version */ + if (argc > 1) { + if (strcmp(argv[1], "--help") == 0) { usage(); - if (r != 1) - goto abort; - } else { - if (errno != ENOENT || cf) { - logger(LOG_ERR, "fopen `%s': %s", cf ? cf : CONFIG, - strerror(errno)); - goto abort; + exit(EXIT_SUCCESS); + } else if (strcmp(argv[1], "--version") == 0) { + printf(""PACKAGE" "VERSION"\n%s\n", copyright); + exit(EXIT_SUCCESS); } } - optind = 0; - while ((opt = getopt_long(argc, argv, OPTS EXTRA_OPTS, - longopts, &option_index)) != -1) + i = 0; + while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) { switch (opt) { - case 'd': - debug++; - switch (debug) { - case 1: - setloglevel(LOG_DEBUG); - break; - case 2: - options->options &= ~DHCPCD_DAEMONISE; - break; - } - break; case 'f': + cffile = optarg; + break; + case 'g': + sig = SIGUSR1; break; case 'k': sig = SIGHUP; @@ -816,235 +1746,291 @@ main(int argc, char **argv) sig = SIGTERM; break; case 'T': - options->options |= DHCPCD_TEST | DHCPCD_PERSISTENT; + i = 1; break; -#ifdef CMDLINE_COMPAT - case 'H': /* FALLTHROUGH */ - case 'M': - del_option_mask(options->requestmask, DHO_MTU); - add_environ(options, "skip_hooks=mtu", 0); + case 'U': + i = 2; break; - case 'N': - del_option_mask(options->requestmask, DHO_NTPSERVER); - add_environ(options, "skip_hooks=ntp.conf", 0); - break; - case 'R': - del_option_mask(options->requestmask, DHO_DNSSERVER); - del_option_mask(options->requestmask, DHO_DNSDOMAIN); - del_option_mask(options->requestmask, DHO_DNSSEARCH); - add_environ(options, "skip_hooks=resolv.conf", 0); - break; - case 'S': - add_option_mask(options->requestmask, DHO_MSCSR); - break; - case 'Y': - del_option_mask(options->requestmask, DHO_NISSERVER); - del_option_mask(options->requestmask, DHO_NISDOMAIN); - add_environ(options, "skip_hooks=yp.conf", 0); - break; -#endif - default: - i = parse_option(opt, optarg, options); - if (i == 1) - break; - if (i == 0) - usage(); - goto abort; + case 'V': + print_options(); + exit(EXIT_SUCCESS); + case '?': + usage(); + exit(EXIT_FAILURE); } } + margv = argv; + margc = argc; + if_options = read_config(cffile, NULL, NULL, NULL); + opt = add_options(if_options, argc, argv); + if (opt != 1) { + if (opt == 0) + usage(); + exit(EXIT_FAILURE); + } + options = if_options->options; + if (i != 0) { + if (i == 1) + options |= DHCPCD_TEST; + else + options |= DHCPCD_DUMPLEASE; + options |= DHCPCD_PERSISTENT; + options &= ~DHCPCD_DAEMONISE; + } + #ifdef THERE_IS_NO_FORK - options->options &= ~DHCPCD_DAEMONISE; + options &= ~DHCPCD_DAEMONISE; #endif -#ifndef ANDROID - /* android runs us as user "dhcp" */ - if (geteuid()) - logger(LOG_WARNING, PACKAGE " will not work correctly unless" - " run as root"); -#endif - - if (options->options & DHCPCD_TEST) { - if (options->options & DHCPCD_REQUEST || - options->options & DHCPCD_INFORM) { - logger(LOG_ERR, - "cannot test with --inform or --request"); - goto abort; + if (options & DHCPCD_DEBUG) + setlogmask(LOG_UPTO(LOG_DEBUG)); + if (options & DHCPCD_QUIET) + close(STDERR_FILENO); + + if (!(options & (DHCPCD_TEST | DHCPCD_DUMPLEASE))) { + /* If we have any other args, we should run as a single dhcpcd + * instance for that interface. */ + len = strlen(PIDFILE) + IF_NAMESIZE + 2; + pidfile = xmalloc(len); + if (optind == argc - 1) + snprintf(pidfile, len, PIDFILE, "-", argv[optind]); + else { + snprintf(pidfile, len, PIDFILE, "", ""); + options |= DHCPCD_MASTER; } + } - if (options->options & DHCPCD_LASTLEASE) { - logger(LOG_ERR, "cannot test with --lastlease"); - goto abort; - } + if (chdir("/") == -1) + syslog(LOG_ERR, "chdir `/': %m"); + atexit(cleanup); - if (sig != 0) { - logger(LOG_ERR, - "cannot test with --release or --renew"); - goto abort; + if (options & DHCPCD_DUMPLEASE) { + if (optind != argc - 1) { + syslog(LOG_ERR, "dumplease requires an interface"); + exit(EXIT_FAILURE); + } + ifaces = iface = xzalloc(sizeof(*iface)); + strlcpy(iface->name, argv[optind], sizeof(iface->name)); + snprintf(iface->leasefile, sizeof(iface->leasefile), + LEASEFILE, iface->name); + iface->state = xzalloc(sizeof(*iface->state)); + iface->state->options = xzalloc(sizeof(*iface->state->options)); + strlcpy(iface->state->options->script, if_options->script, + sizeof(iface->state->options->script)); + iface->state->new = read_lease(iface); + if (iface->state->new == NULL && errno == ENOENT) { + strlcpy(iface->leasefile, argv[optind], + sizeof(iface->leasefile)); + iface->state->new = read_lease(iface); } + if (iface->state->new == NULL) { + if (errno == ENOENT) + syslog(LOG_ERR, "%s: no lease to dump", + iface->name); + exit(EXIT_FAILURE); + } + iface->state->reason = "DUMP"; + run_script(iface); + exit(EXIT_SUCCESS); } - prefix = xmalloc(sizeof(char) * (IF_NAMESIZE + 3)); - snprintf(prefix, IF_NAMESIZE, "%s: ", options->interface); - setlogprefix(prefix); - snprintf(options->pidfile, sizeof(options->pidfile), PIDFILE, - options->interface); - free(prefix); - - if (options->request_address.s_addr == 0 && - (options->options & DHCPCD_INFORM || - options->options & DHCPCD_REQUEST)) - { - errno = 0; - if (get_address(options->interface, - &options->request_address, - &options->request_netmask) != 1) - { - if (errno) - logger(LOG_ERR, "get_address: %s", - strerror(errno)); - else - logger(LOG_ERR, "no existing address"); - goto abort; + if (!(options & (DHCPCD_MASTER | DHCPCD_TEST))) { + control_fd = open_control(); + if (control_fd != -1) { + syslog(LOG_INFO, + "sending commands to master dhcpcd process"); + i = send_control(argc, argv); + if (i > 0) { + syslog(LOG_DEBUG, "send OK"); + exit(EXIT_SUCCESS); + } else { + syslog(LOG_ERR, "failed to send commands"); + exit(EXIT_FAILURE); + } + } else { + if (errno != ENOENT) + syslog(LOG_ERR, "open_control: %m"); } } - if (IN_LINKLOCAL(ntohl(options->request_address.s_addr))) { - logger(LOG_ERR, - "you are not allowed to request a link local address"); - goto abort; - } - if (chdir("/") == -1) - logger(LOG_ERR, "chdir `/': %s", strerror(errno)); - umask(022); - - if (sig != 0 && !(options->options & DHCPCD_DAEMONISED)) { -#ifdef ANDROID - char pidpropname[PROPERTY_KEY_MAX]; - char pidpropval[PROPERTY_VALUE_MAX]; - - i = -1; - if (snprintf(pidpropname, - sizeof(pidpropname), - "dhcp.%s.pid", options->interface) >= PROPERTY_KEY_MAX) { - goto abort; - } - property_get(pidpropname, pidpropval, NULL); - if (strlen(pidpropval) == 0) { - goto abort; - } - pid = atoi(pidpropval); -#else - i = -1; - pid = read_pid(options->pidfile); -#endif - if (pid != 0) - logger(LOG_INFO, "sending signal %d to pid %d", - sig, pid); + if (geteuid()) + syslog(LOG_WARNING, + PACKAGE " will not work correctly unless run as root"); - if (!pid || (i = kill(pid, sig))) { + if (sig != 0) { + pid = read_pid(); + if (pid != 0) + syslog(LOG_INFO, "sending signal %d to pid %d", + sig, pid); + if (pid == 0 || kill(pid, sig) != 0) { if (sig != SIGALRM) - logger(LOG_ERR, ""PACKAGE" not running"); - unlink(options->pidfile); - } - if (i == 0) { - if (sig == SIGALRM) { - retval = EXIT_SUCCESS; - goto abort; + syslog(LOG_ERR, ""PACKAGE" not running"); + if (pid != 0 && errno != ESRCH) { + syslog(LOG_ERR, "kill: %m"); + exit(EXIT_FAILURE); } + unlink(pidfile); + if (sig != SIGALRM) + exit(EXIT_FAILURE); + } else { + if (sig == SIGALRM) + exit(EXIT_SUCCESS); /* Spin until it exits */ - logger(LOG_INFO, "waiting for pid %d to exit", pid); + syslog(LOG_INFO, "waiting for pid %d to exit", pid); ts.tv_sec = 0; ts.tv_nsec = 100000000; /* 10th of a second */ for(i = 0; i < 100; i++) { nanosleep(&ts, NULL); - if (read_pid(options->pidfile) == 0) { - retval = EXIT_SUCCESS; - break; - } + if (read_pid() == 0) + exit(EXIT_SUCCESS); } - if (retval != EXIT_SUCCESS) - logger(LOG_ERR, "pid %d failed to exit", pid); - goto abort; + syslog(LOG_ERR, "pid %d failed to exit", pid); + exit(EXIT_FAILURE); } - if (sig != SIGALRM) - goto abort; } - if (!(options->options & DHCPCD_TEST) && - !(options->options & DHCPCD_DAEMONISED)) - { -#ifdef ANDROID - char pidpropname[PROPERTY_KEY_MAX]; - char pidpropval[PROPERTY_VALUE_MAX]; -#endif -#ifndef ANDROID - if ((pid = read_pid(options->pidfile)) > 0 && + if (!(options & DHCPCD_TEST)) { + if ((pid = read_pid()) > 0 && kill(pid, 0) == 0) { - logger(LOG_ERR, ""PACKAGE - " already running on pid %d (%s)", - pid, options->pidfile); - goto abort; + syslog(LOG_ERR, ""PACKAGE + " already running on pid %d (%s)", + pid, pidfile); + exit(EXIT_FAILURE); } -#endif - pid_fd = open(options->pidfile, - O_WRONLY | O_CREAT | O_NONBLOCK, 0664); - if (pid_fd == -1) { - logger(LOG_ERR, "open `%s': %s", - options->pidfile, strerror(errno)); - goto abort; + + /* Ensure we have the needed directories */ + if (mkdir(RUNDIR, 0755) == -1 && errno != EEXIST) { + syslog(LOG_ERR, "mkdir `%s': %m", RUNDIR); + exit(EXIT_FAILURE); + } + if (mkdir(DBDIR, 0755) == -1 && errno != EEXIST) { + syslog(LOG_ERR, "mkdir `%s': %m", DBDIR); + exit(EXIT_FAILURE); } + pidfd = open(pidfile, O_WRONLY | O_CREAT | O_NONBLOCK, 0664); + if (pidfd == -1) { + syslog(LOG_ERR, "open `%s': %m", pidfile); + exit(EXIT_FAILURE); + } /* Lock the file so that only one instance of dhcpcd runs * on an interface */ - if (flock(pid_fd, LOCK_EX | LOCK_NB) == -1) { - logger(LOG_ERR, "flock `%s': %s", - options->pidfile, strerror(errno)); - goto abort; + if (flock(pidfd, LOCK_EX | LOCK_NB) == -1) { + syslog(LOG_ERR, "flock `%s': %m", pidfile); + exit(EXIT_FAILURE); } + if (set_cloexec(pidfd) == -1) + exit(EXIT_FAILURE); + writepid(pidfd, getpid()); + } - if (set_cloexec(pid_fd) == -1) - goto abort; -#ifdef ANDROID - if (snprintf(pidpropname, - sizeof(pidpropname), - "dhcp.%s.pid", options->interface) >= PROPERTY_KEY_MAX) { - goto abort; - } - if (snprintf(pidpropval, sizeof(pidpropval), "%d", getpid()) >= PROPERTY_VALUE_MAX) { - goto abort; - } - property_set(pidpropname, pidpropval); -#else - writepid(pid_fd, getpid()); -#endif - logger(LOG_INFO, PACKAGE " " VERSION " starting"); + syslog(LOG_INFO, "version " VERSION " starting"); + + if ((signal_fd = signal_init()) == -1) + exit(EXIT_FAILURE); + if (signal_setup() == -1) + exit(EXIT_FAILURE); + add_event(signal_fd, handle_signal, NULL); + + if (options & DHCPCD_MASTER) { + if (start_control() == -1) { + syslog(LOG_ERR, "start_control: %m"); + exit(EXIT_FAILURE); + } } - /* Terminate the encapsulated options */ - if (options->vendor[0]) { - options->vendor[0]++; - options->vendor[options->vendor[0]] = DHO_END; + if (init_sockets() == -1) { + syslog(LOG_ERR, "init_socket: %m"); + exit(EXIT_FAILURE); + } + if (if_options->options & DHCPCD_LINK) { + linkfd = open_link_socket(); + if (linkfd == -1) + syslog(LOG_ERR, "open_link_socket: %m"); + else + add_event(linkfd, handle_link, NULL); } - if (dhcp_run(options, &pid_fd) == 0) - retval = EXIT_SUCCESS; + ifc = argc - optind; + ifv = argv + optind; -abort: - /* If we didn't daemonise then we need to punt the pidfile now */ - if (pid_fd > -1) { - close(pid_fd); - unlink(options->pidfile); + /* When running dhcpcd against a single interface, we need to retain + * the old behaviour of waiting for an IP address */ + if (ifc == 1) + options |= DHCPCD_WAITIP; + + ifaces = discover_interfaces(ifc, ifv); + for (i = 0; i < ifc; i++) { + for (iface = ifaces; iface; iface = iface->next) + if (strcmp(iface->name, ifv[i]) == 0) + break; + if (!iface) + syslog(LOG_ERR, "%s: interface not found or invalid", + ifv[i]); } - if (options->environ) { - len = 0; - while (options->environ[len]) - free(options->environ[len++]); - free(options->environ); + if (!ifaces) { + if (ifc == 0) + syslog(LOG_ERR, "no valid interfaces found"); + else + exit(EXIT_FAILURE); + if (!(options & DHCPCD_LINK)) { + syslog(LOG_ERR, + "aborting as link detection is disabled"); + exit(EXIT_FAILURE); + } + } + + if (options & DHCPCD_BACKGROUND) + daemonise(); + + opt = 0; + for (iface = ifaces; iface; iface = iface->next) { + init_state(iface, argc, argv); + if (iface->carrier != LINK_DOWN) + opt = 1; + } + + if (!(options & DHCPCD_BACKGROUND)) { + /* If we don't have a carrier, we may have to wait for a second + * before one becomes available if we brought an interface up. */ + if (opt == 0 && + options & DHCPCD_LINK && + options & DHCPCD_WAITUP && + !(options & DHCPCD_WAITIP)) + { + ts.tv_sec = 1; + ts.tv_nsec = 0; + nanosleep(&ts, NULL); + for (iface = ifaces; iface; iface = iface->next) { + handle_carrier(iface->name); + if (iface->carrier != LINK_DOWN) { + opt = 1; + break; + } + } + } + if (opt == 0 && + options & DHCPCD_LINK && + !(options & DHCPCD_WAITIP)) + { + syslog(LOG_WARNING, "no interfaces have a carrier"); + daemonise(); + } else if (if_options->timeout > 0) { + if (options & DHCPCD_IPV4LL) + options |= DHCPCD_TIMEOUT_IPV4LL; + add_timeout_sec(if_options->timeout, + handle_exit_timeout, NULL); + } } - free(options->blacklist); - free(options); - exit(retval); - /* NOTREACHED */ + free_options(if_options); + if_options = NULL; + + sort_interfaces(); + for (iface = ifaces; iface; iface = iface->next) + add_timeout_sec(0, start_interface, iface); + + start_eloop(); + exit(EXIT_SUCCESS); } |