diff --git a/RELNOTES b/RELNOTES index 1f583328..a409cc6d 100644 --- a/RELNOTES +++ b/RELNOTES @@ -81,6 +81,10 @@ and for prodding me into improving it. their arguments from upper to lower and lower to upper cases respectively. Thanks to a patch from Albert Herranz. +- The dhclient 'reject ...;' statement, which rejects leases given by named + server-identifiers, now permits address ranges to be specified in CIDR + notation. + Changes since 3.0.4 - A warning that host statements declared within subnet or shared-network diff --git a/client/clparse.c b/client/clparse.c index dbd54ebe..4af95e15 100644 --- a/client/clparse.c +++ b/client/clparse.c @@ -34,7 +34,7 @@ #ifndef lint static char copyright[] = -"$Id: clparse.c,v 1.65 2006/02/24 23:16:27 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium. All rights reserved.\n"; +"$Id: clparse.c,v 1.66 2006/05/15 15:07:49 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium. All rights reserved.\n"; #endif /* not lint */ #include "dhcpd.h" @@ -1124,24 +1124,49 @@ void parse_reject_statement (cfile, config) { int token; const char *val; - struct iaddr addr; - struct iaddrlist *list; + struct iaddrmatch match; + struct iaddrmatchlist *list; + int i; do { - if (!parse_ip_addr (cfile, &addr)) { - parse_warn (cfile, "expecting IP address."); + if (!parse_ip_addr_with_subnet (cfile, &match)) { + /* no warn: parser will have reported what's wrong */ skip_to_semi (cfile); return; } - list = (struct iaddrlist *)dmalloc (sizeof (struct iaddrlist), - MDL); + /* check mask is not all zeros (because that would + * reject EVERY address). This check could be + * simplified if we assume that the mask *always* + * represents a prefix .. but perhaps it might be + * useful to have a mask which is not a proper prefix + * (perhaps for ipv6?). The following is almost as + * efficient as inspection of match.mask.iabuf[0] when + * it IS a true prefix, and is more general when it is + * not. + */ + + for (i=0 ; i < match.mask.len ; i++) { + if (match.mask.iabuf[i]) { + break; + } + } + + if (i == match.mask.len) { + /* oops we found all zeros */ + parse_warn(cfile, "zero-length prefix is not permitted " + "for reject statement"); + skip_to_semi(cfile); + return; + } + + list = dmalloc(sizeof(struct iaddrmatchlist), MDL); if (!list) log_fatal ("no memory for reject list!"); - list -> addr = addr; - list -> next = config -> reject_list; - config -> reject_list = list; + list->match = match; + list->next = config->reject_list; + config->reject_list = list; token = next_token (&val, (unsigned *)0, cfile); } while (token == COMMA); diff --git a/client/dhclient.c b/client/dhclient.c index 8485364f..c58135a7 100644 --- a/client/dhclient.c +++ b/client/dhclient.c @@ -32,7 +32,7 @@ #ifndef lint static char ocopyright[] = -"$Id: dhclient.c,v 1.137 2006/05/11 14:48:58 shane Exp $ Copyright (c) 2004-2006 Internet Systems Consortium. All rights reserved.\n"; +"$Id: dhclient.c,v 1.138 2006/05/15 15:07:49 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium. All rights reserved.\n"; #endif /* not lint */ #include "dhcpd.h" @@ -1004,7 +1004,9 @@ void db_startup (testp) void bootp (packet) struct packet *packet; { - struct iaddrlist *ap; + struct iaddrmatchlist *ap; + char addrbuf[4*16]; + char maskbuf[4*16]; if (packet -> raw -> op != BOOTREPLY) return; @@ -1013,9 +1015,17 @@ void bootp (packet) on it. */ for (ap = packet -> interface -> client -> config -> reject_list; ap; ap = ap -> next) { - if (addr_eq (packet -> client_addr, ap -> addr)) { - log_info ("BOOTREPLY from %s rejected.", - piaddr (ap -> addr)); + if (addr_match(&packet->client_addr, &ap->match)) { + + /* piaddr() returns its result in a static + buffer sized 4*16 (see common/inet.c). */ + + strcpy(addrbuf, piaddr(ap->match.addr)); + strcpy(maskbuf, piaddr(ap->match.mask)); + + log_info("BOOTREPLY from %s rejected by rule %s " + "mask %s.", piaddr(packet->client_addr), + addrbuf, maskbuf); return; } } @@ -1027,9 +1037,11 @@ void bootp (packet) void dhcp (packet) struct packet *packet; { - struct iaddrlist *ap; + struct iaddrmatchlist *ap; void (*handler) PROTO ((struct packet *)); const char *type; + char addrbuf[4*16]; + char maskbuf[4*16]; switch (packet -> packet_type) { case DHCPOFFER: @@ -1055,9 +1067,17 @@ void dhcp (packet) on it. */ for (ap = packet -> interface -> client -> config -> reject_list; ap; ap = ap -> next) { - if (addr_eq (packet -> client_addr, ap -> addr)) { - log_info ("%s from %s rejected.", - type, piaddr (ap -> addr)); + if (addr_match(&packet->client_addr, &ap->match)) { + + /* piaddr() returns its result in a static + buffer sized 4*16 (see common/inet.c). */ + + strcpy(addrbuf, piaddr(ap->match.addr)); + strcpy(maskbuf, piaddr(ap->match.mask)); + + log_info("%s from %s rejected by rule %s mask %s.", + type, piaddr(packet->client_addr), + addrbuf, maskbuf); return; } } diff --git a/client/dhclient.conf.5 b/client/dhclient.conf.5 index 49a46b50..73ec86cd 100644 --- a/client/dhclient.conf.5 +++ b/client/dhclient.conf.5 @@ -1,4 +1,4 @@ -.\" $Id: dhclient.conf.5,v 1.16 2005/07/07 16:39:07 dhankins Exp $ +.\" $Id: dhclient.conf.5,v 1.17 2006/05/15 15:07:49 dhankins Exp $ .\" .\" Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") .\" Copyright (c) 1996-2003 by Internet Software Consortium @@ -28,7 +28,7 @@ .\" see ``http://www.vix.com''. To learn more about Nominum, Inc., see .\" ``http://www.nominum.com''. .\" -.\" $Id: dhclient.conf.5,v 1.16 2005/07/07 16:39:07 dhankins Exp $ +.\" $Id: dhclient.conf.5,v 1.17 2006/05/15 15:07:49 dhankins Exp $ .\" .TH dhclient.conf 5 .SH NAME @@ -504,15 +504,28 @@ declaration for the IP alias address, and a subnet-mask option declaration. A medium statement should never be included in an alias declaration. .SH OTHER DECLARATIONS - \fBreject \fIip-address\fB;\fR + \fBreject \fIcidr-ip-address\fR [\fB,\fR \fI...\fB \fIcidr-ip-address\fR ] \fB;\fR .PP The .B reject statement causes the DHCP client to reject offers from -servers who use the specified address as a server identifier. This -can be used to avoid being configured by rogue or misconfigured dhcp -servers, although it should be a last resort - better to track down -the bad DHCP server and fix it. +servers whose server identifier matches any of the specified hosts or +subnets. This can be used to avoid being configured by rogue or +misconfigured dhcp servers, although it should be a last resort - +better to track down the bad DHCP server and fix it. +.PP +The \fIcidr-ip-address\fR configuration type is of the +form \fIip-address\fR[\fB/\fIprefixlen\fR], where \fIip-address\fR is a +dotted quad IP address, and \fRprefixlen\fR is the CIDR prefix length of +the subnet, counting the number of significant bits in the netmask starting +from the leftmost end. Example configuration syntax: +.PP +.I \fIreject\fR 192.168.0.0\fB/\fR16\fB,\fR 10.0.0.5\fB;\fR +.RE +.PP +The above example would cause offers from any server identifier in the +entire RFC 1918 "Class C" network 192.168.0.0/16, or the specific +single address 10.0.0.5, to be rejected. .PP \fBinterface "\fIname\fB" { \fIdeclarations ... \fB } .PP diff --git a/common/inet.c b/common/inet.c index e272bd21..934eba19 100644 --- a/common/inet.c +++ b/common/inet.c @@ -35,7 +35,7 @@ #ifndef lint static char copyright[] = -"$Id: inet.c,v 1.10 2005/03/17 20:14:58 dhankins Exp $ Copyright (c) 2004-2005 Internet Systems Consortium. All rights reserved.\n"; +"$Id: inet.c,v 1.11 2006/05/15 15:07:49 dhankins Exp $ Copyright (c) 2004-2005 Internet Systems Consortium. All rights reserved.\n"; #endif /* not lint */ #include "dhcpd.h" @@ -185,6 +185,31 @@ int addr_eq (addr1, addr2) return memcmp (addr1.iabuf, addr2.iabuf, addr1.len) == 0; } +/* addr_match + * + * compares an IP address against a network/mask combination + * by ANDing the IP with the mask and seeing whether the result + * matches the masked network value. + */ +int +addr_match(addr, match) + struct iaddr *addr; + struct iaddrmatch *match; +{ + int i; + + if (addr->len != match->addr.len) + return 0; + + i = 0; + for (i = 0 ; i < addr->len ; i++) { + if ((addr->iabuf[i] & match->mask.iabuf[i]) != + match->addr.iabuf[i]) + return 0; + } + return 1; +} + char *piaddr (addr) struct iaddr addr; { diff --git a/common/parse.c b/common/parse.c index 8aefd5d6..247e39b7 100644 --- a/common/parse.c +++ b/common/parse.c @@ -34,7 +34,7 @@ #ifndef lint static char copyright[] = -"$Id: parse.c,v 1.108 2006/05/11 16:31:29 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium. All rights reserved.\n"; +"$Id: parse.c,v 1.109 2006/05/15 15:07:49 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium. All rights reserved.\n"; #endif /* not lint */ #include "dhcpd.h" @@ -321,6 +321,98 @@ int parse_ip_addr (cfile, addr) return 0; } +/* + * ip-address-with-subnet :== ip-address | + * ip-address "/" NUMBER + */ + +int +parse_ip_addr_with_subnet(cfile, match) + struct parse *cfile; + struct iaddrmatch *match; +{ + const char *val, *orig; + enum dhcp_token token; + int prefixlen; + int fflen; + unsigned char newval, warnmask=0; + + if (parse_ip_addr(cfile, &match->addr)) { + /* default to host mask */ + prefixlen = match->addr.len * 8; + + token = peek_token(&val, NULL, cfile); + + if (token == SLASH) { + next_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + + if (token != NUMBER) { + parse_warn(cfile, "Invalid CIDR prefix length:" + " expecting a number."); + return 0; + } + + prefixlen = atoi(val); + + if (prefixlen < 0 || + prefixlen > (match->addr.len * 8)) { + parse_warn(cfile, "subnet prefix is out of " + "range [0..%d].", + match->addr.len * 8); + return 0; + } + } + + /* construct a suitable mask field */ + + /* copy length */ + match->mask.len = match->addr.len; + + /* count of 0xff bytes in mask */ + fflen = prefixlen / 8; + + /* set leading mask */ + memset(match->mask.iabuf, 0xff, fflen); + + /* set zeroes */ + if (fflen < match->mask.len) { + match->mask.iabuf[fflen] = + "\x00\x80\xc0\xe0\xf0\xf8\xfc\xfe"[prefixlen % 8]; + + memset(match->mask.iabuf+fflen+1, 0x00, + match->mask.len - fflen - 1); + + /* AND-out insignificant bits from supplied netmask. */ + orig = piaddr(match->addr); + do { + newval = match->addr.iabuf[fflen] & + match->mask.iabuf[fflen]; + + if (newval != match->addr.iabuf[fflen]) { + warnmask = 1; + match->addr.iabuf[fflen] = newval; + } + } while (++fflen < match->mask.len); + + if (warnmask) { + log_error("Warning: Extraneous bits removed " + "in address component of %s/%d.", + orig, prefixlen); + log_error("New value: %s/%d.", + piaddr(match->addr), prefixlen); + } + } + + return 1; + } + + parse_warn(cfile, + "expecting ip-address or ip-address/prefixlen"); + + return 0; /* let caller pick up pieces */ +} + /* * hardware-parameter :== HARDWARE hardware-type colon-seperated-hex-list SEMI * hardware-type :== ETHERNET | TOKEN_RING | FDDI diff --git a/includes/dhcpd.h b/includes/dhcpd.h index 55b9a740..9ee2fd98 100644 --- a/includes/dhcpd.h +++ b/includes/dhcpd.h @@ -744,7 +744,7 @@ struct client_config { authenticate. */ struct string_list *medium; /* Current network medium. */ - struct iaddrlist *reject_list; /* Servers to reject. */ + struct iaddrmatchlist *reject_list; /* Servers to reject. */ int omapi_port; /* port on which to accept OMAPI connections, or -1 for no @@ -1876,6 +1876,7 @@ struct iaddr ip_addr PROTO ((struct iaddr, struct iaddr, u_int32_t)); struct iaddr broadcast_addr PROTO ((struct iaddr, struct iaddr)); u_int32_t host_addr PROTO ((struct iaddr, struct iaddr)); int addr_eq PROTO ((struct iaddr, struct iaddr)); +int addr_match(struct iaddr *, struct iaddrmatch *); char *piaddr PROTO ((struct iaddr)); char *piaddrmask (struct iaddr, struct iaddr, const char *, int); diff --git a/includes/inet.h b/includes/inet.h index d037d78b..ddc2d9ea 100644 --- a/includes/inet.h +++ b/includes/inet.h @@ -43,3 +43,27 @@ struct iaddrlist { struct iaddrlist *next; struct iaddr addr; }; + + +/* struct iaddrmatch - used to compare a host IP against a subnet spec + * + * There is a space/speed tradeoff here implied by the use of a second + * struct iaddr to hold the mask; while using an unsigned (byte!) to + * represent the subnet prefix length would be more memory efficient, + * it makes run-time mask comparisons more expensive. Since such + * entries are used currently only in restricted circumstances + * (wanting to reject a subnet), the decision is in favour of run-time + * efficiency. + */ + +struct iaddrmatch { + struct iaddr addr; + struct iaddr mask; +}; + +/* its list ... */ + +struct iaddrmatchlist { + struct iaddrmatchlist *next; + struct iaddrmatch match; +};