diff options
Diffstat (limited to 'usr.sbin/dhcpd')
-rw-r--r-- | usr.sbin/dhcpd/Makefile | 4 | ||||
-rw-r--r-- | usr.sbin/dhcpd/dhcp.c | 15 | ||||
-rw-r--r-- | usr.sbin/dhcpd/dhcpd.8 | 41 | ||||
-rw-r--r-- | usr.sbin/dhcpd/dhcpd.c | 44 | ||||
-rw-r--r-- | usr.sbin/dhcpd/dhcpd.h | 19 | ||||
-rw-r--r-- | usr.sbin/dhcpd/memory.c | 33 | ||||
-rw-r--r-- | usr.sbin/dhcpd/pfutils.c | 182 |
7 files changed, 325 insertions, 13 deletions
diff --git a/usr.sbin/dhcpd/Makefile b/usr.sbin/dhcpd/Makefile index d2f31c667b4..2429bf89325 100644 --- a/usr.sbin/dhcpd/Makefile +++ b/usr.sbin/dhcpd/Makefile @@ -1,10 +1,10 @@ -# $OpenBSD: Makefile,v 1.2 2004/04/20 23:01:09 henning Exp $ +# $OpenBSD: Makefile,v 1.3 2006/05/31 02:43:15 ckuethe Exp $ .include <bsd.own.mk> SRCS= bootp.c confpars.c db.c dhcp.c dhcpd.c bpf.c packet.c errwarn.c \ dispatch.c print.c memory.c options.c inet.c conflex.c parse.c \ - alloc.c tables.c tree.c hash.c convert.c icmp.c + alloc.c tables.c tree.c hash.c convert.c icmp.c pfutils.c PROG= dhcpd MAN= dhcpd.8 dhcpd.conf.5 dhcpd.leases.5 dhcp-options.5 diff --git a/usr.sbin/dhcpd/dhcp.c b/usr.sbin/dhcpd/dhcp.c index 47a219df000..b6f04d044f4 100644 --- a/usr.sbin/dhcpd/dhcp.c +++ b/usr.sbin/dhcpd/dhcp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dhcp.c,v 1.22 2006/03/16 15:44:40 claudio Exp $ */ +/* $OpenBSD: dhcp.c,v 1.23 2006/05/31 02:43:15 ckuethe Exp $ */ /* * Copyright (c) 1995, 1996, 1997, 1998, 1999 @@ -39,6 +39,10 @@ */ #include "dhcpd.h" +extern int pfpipe[2]; +extern int gotpipe; +extern char *abandoned_tab; +extern char *changedmac_tab; int outstanding_pings; @@ -81,6 +85,7 @@ dhcpdiscover(struct packet *packet) { struct lease *lease = find_lease(packet, packet->shared_network, 0); struct host_decl *hp; + struct pf_cmd cmd; note("DHCPDISCOVER from %s via %s", print_hw_addr(packet->raw->htype, packet->raw->hlen, @@ -135,6 +140,14 @@ dhcpdiscover(struct packet *packet) warning("Reclaiming abandoned IP address %s.", piaddr(lease->ip_addr)); lease->flags &= ~ABANDONED_LEASE; + + if (gotpipe && (abandoned_tab != NULL)){ + cmd.type = 'L'; + bcopy(lease->ip_addr.iabuf, + &cmd.ip.s_addr, 4); + (void)atomicio(vwrite, pfpipe[1], + &cmd, sizeof(struct pf_cmd)); + } } } diff --git a/usr.sbin/dhcpd/dhcpd.8 b/usr.sbin/dhcpd/dhcpd.8 index f5fd80b7027..c388d34d0c8 100644 --- a/usr.sbin/dhcpd/dhcpd.8 +++ b/usr.sbin/dhcpd/dhcpd.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: dhcpd.8,v 1.8 2005/09/30 20:34:26 jaredy Exp $ +.\" $OpenBSD: dhcpd.8,v 1.9 2006/05/31 02:43:15 ckuethe Exp $ .\" .\" Copyright (c) 1995, 1996 The Internet Software Consortium. .\" All rights reserved. @@ -47,6 +47,9 @@ .Op Fl dfn .Op Fl c Ar config-file .Op Fl l Ar lease-file +.Op Fl p Ar pf-device +.Op Fl A Ar abandoned_ip_table +.Op Fl C Ar changed_ip_table .Op Ar if0 Op Ar ... ifN .Sh DESCRIPTION The Internet Software Consortium DHCP Server, @@ -75,6 +78,11 @@ When a client requests an address using the DHCP protocol, allocates an address for it. Each client is assigned a lease, which expires after an amount of time chosen by the administrator (by default, one day). +When a leased IP address is assigned to a new hardware address, +.Nm +may delete the leased IP from certain +.Xr pf 4 +tables. Before leases expire, the clients to which leases are assigned are expected to renew them in order to continue to use the addresses. Once a lease has expired, the client to which that lease was assigned is no @@ -185,6 +193,37 @@ running in production, this option should be used .Em only for testing lease files in a non-production environment. +.It Fl p Ar pf-device +Use an alternate pf device, +.Ar pf-device . +.It Fl A Ar abandoned_ip_table +When an address is abandoned for some reason, add it to the +.Xr pf 4 +table named +.Ar abandoned_ip_table . +This can be used to defend against machines "camping" on an address +without obtaining a lease. +When an address is properly leased, +.Nm +will remove the address from this table. +.It Fl C Ar changed_ip_table +When an address is leased to a different hardware address, delete it from the +.Xr pf 4 +table named +.Ar changed_ip_table . +This feature complements the overload table in a stateful +.Xr pf 4 +rule. +If a host appears to be misbehaving, it can be quarantined by using the +overload feature. +When the address is leased to a different machine, +.Nm +can remove the address from the overload table, thus allowing a well-behaved +machine to reuse the address. +Users are cautioned against placing much trust in ethernet or IP addresses; +.Xr ifconfig 8 +can be used to trivially change the interface's address, and on a busy DHCP +network, IP addresses will likely be quickly recycled. .It Fl n Only test configuration, do not run .Nm . diff --git a/usr.sbin/dhcpd/dhcpd.c b/usr.sbin/dhcpd/dhcpd.c index a2ba8fc4bc6..d19a458866c 100644 --- a/usr.sbin/dhcpd/dhcpd.c +++ b/usr.sbin/dhcpd/dhcpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dhcpd.c,v 1.25 2006/05/11 01:19:08 krw Exp $ */ +/* $OpenBSD: dhcpd.c,v 1.26 2006/05/31 02:43:15 ckuethe Exp $ */ /* * Copyright (c) 2004 Henning Brauer <henning@cvs.openbsd.org> @@ -50,24 +50,35 @@ struct group root_group; u_int16_t server_port; u_int16_t client_port; +struct passwd *pw; int log_priority; int log_perror = 0; +int pfpipe[2]; +int gotpipe = 0; +pid_t pfproc_pid = -1; char *path_dhcpd_conf = _PATH_DHCPD_CONF; char *path_dhcpd_db = _PATH_DHCPD_DB; +char *abandoned_tab = NULL; +char *changedmac_tab = NULL; int main(int argc, char *argv[]) { int ch, cftest = 0, daemonize = 1; - struct passwd *pw; extern char *__progname; /* Initially, log errors to stderr as well as to syslogd. */ openlog(__progname, LOG_NDELAY, DHCPD_LOG_FACILITY); setlogmask(LOG_UPTO(LOG_INFO)); - while ((ch = getopt(argc, argv, "c:dfl:nq")) != -1) + while ((ch = getopt(argc, argv, "A:C:c:dfl:nq")) != -1) switch (ch) { + case 'A': + abandoned_tab = optarg; + break; + case 'C': + changedmac_tab = optarg; + break; case 'c': path_dhcpd_conf = optarg; break; @@ -129,6 +140,26 @@ main(int argc, char *argv[]) if (daemonize) daemon(0, 0); + /* don't go near /dev/pf unless we actually intend to use it */ + if ((abandoned_tab != NULL) || (changedmac_tab != NULL)){ + if (pipe(pfpipe) == -1) + error("pipe (%m)"); + switch (pfproc_pid = fork()){ + case -1: + error("fork (%m)"); + /* NOTREACHED */ + exit(1); + case 0: + /* child process. start up table engine */ + pftable_handler(); + /* NOTREACHED */ + exit(1); + default: + gotpipe = 1; + break; + } + } + if (chroot(_PATH_VAREMPTY) == -1) error("chroot %s: %m", _PATH_VAREMPTY); if (chdir("/") == -1) @@ -150,9 +181,10 @@ usage(void) { extern char *__progname; - fprintf(stderr, "usage: %s [-dfn] [-c config-file] [-l lease-file]", - __progname); - fprintf(stderr, " [if0 [...ifN]]\n"); + fprintf(stderr, "usage: %s [-dfn] [-c config-file]", __progname); + fprintf(stderr, " [-l lease-file] [-p pf-device]\n"); + fprintf(stderr, " [-A abandoned_ip_table]"); + fprintf(stderr, " [-C changed_ip_table] [if0 [...ifN]]\n"); exit(1); } diff --git a/usr.sbin/dhcpd/dhcpd.h b/usr.sbin/dhcpd/dhcpd.h index e706fd98349..1356597021c 100644 --- a/usr.sbin/dhcpd/dhcpd.h +++ b/usr.sbin/dhcpd/dhcpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: dhcpd.h,v 1.18 2006/05/30 23:43:46 ckuethe Exp $ */ +/* $OpenBSD: dhcpd.h,v 1.19 2006/05/31 02:43:15 ckuethe Exp $ */ /* * Copyright (c) 1995, 1996, 1997, 1998, 1999 @@ -84,6 +84,10 @@ extern int h_errno; #define _PATH_DHCPD_DB "/var/db/dhcpd.leases" #endif +#ifndef _PATH_DEV_PF +#define _PATH_DEV_PF "/dev/pf" +#endif + /* Time stuff... */ #include <sys/time.h> @@ -326,6 +330,12 @@ struct client_lease { struct option_data options [256]; /* Options supplied with lease. */ }; +/* privsep message. fixed length for easy parsing */ +struct pf_cmd{ + struct in_addr ip; + u_int32_t type; +}; + /* Possible states in which the client can be. */ enum dhcp_state { S_REBOOTING, @@ -775,3 +785,10 @@ u_int32_t wrapsum(u_int32_t); void icmp_startup(int, void (*)(struct iaddr, u_int8_t *, int)); int icmp_echorequest(struct iaddr *); void icmp_echoreply(struct protocol *); + +/* pfutils.c */ +__dead void pftable_handler(void); +void pf_change_table(int , int , struct in_addr , char *); +void pf_kill_state(int , struct in_addr ); +size_t atomicio(ssize_t (*)(int, void *, size_t), int, void *, size_t); +#define vwrite (ssize_t (*)(int, void *, size_t))write diff --git a/usr.sbin/dhcpd/memory.c b/usr.sbin/dhcpd/memory.c index 614f881dbfc..26366396bf9 100644 --- a/usr.sbin/dhcpd/memory.c +++ b/usr.sbin/dhcpd/memory.c @@ -1,4 +1,4 @@ -/* $OpenBSD: memory.c,v 1.10 2004/09/21 04:07:04 david Exp $ */ +/* $OpenBSD: memory.c,v 1.11 2006/05/31 02:43:15 ckuethe Exp $ */ /* * Copyright (c) 1995, 1996, 1997, 1998 The Internet Software Consortium. @@ -39,6 +39,10 @@ */ #include "dhcpd.h" +extern int pfpipe[2]; +extern int gotpipe; +extern char *abandoned_tab; +extern char *changedmac_tab; static struct subnet *subnets; static struct shared_network *shared_networks; @@ -435,7 +439,9 @@ supersede_lease(struct lease *comp, struct lease *lease, int commit) { int enter_uid = 0; int enter_hwaddr = 0; + int do_pftable = 0; struct lease *lp; + struct pf_cmd cmd; /* Static leases are not currently kept in the database... */ if (lease->flags & STATIC_LEASE) @@ -489,8 +495,11 @@ supersede_lease(struct lease *comp, struct lease *lease, int commit) comp->hardware_addr.hlen))) { hw_hash_delete(comp); enter_hwaddr = 1; - } else if (!comp->hardware_addr.htype) + do_pftable = 1; + } else if (!comp->hardware_addr.htype) { enter_hwaddr = 1; + do_pftable = 1; + } /* Copy the data files, but not the linkages. */ comp->starts = lease->starts; @@ -595,6 +604,18 @@ supersede_lease(struct lease *comp, struct lease *lease, int commit) comp->ends = lease->ends; } + if (gotpipe && (abandoned_tab != NULL)){ + cmd.type = 'L'; + bcopy(lease->ip_addr.iabuf, &cmd.ip.s_addr, 4); + (void)atomicio(vwrite, pfpipe[1], &cmd, sizeof(struct pf_cmd)); + } + + if (gotpipe && do_pftable && (changedmac_tab != NULL)){ + cmd.type = 'C'; + bcopy(lease->ip_addr.iabuf, &cmd.ip.s_addr, 4); + (void)atomicio(vwrite, pfpipe[1], &cmd, sizeof(struct pf_cmd)); + } + /* Return zero if we didn't commit the lease to permanent storage; nonzero if we did. */ return commit && write_lease(comp) && commit_leases(); @@ -626,6 +647,7 @@ void abandon_lease(struct lease *lease, char *message) { struct lease lt; + struct pf_cmd cmd; time_t abtime; abtime = lease->subnet->group->default_lease_time; @@ -639,6 +661,13 @@ abandon_lease(struct lease *lease, char *message) lt.uid = NULL; lt.uid_len = 0; supersede_lease(lease, <, 1); + + if (gotpipe && abandoned_tab != NULL){ + cmd.type = 'A'; + bcopy(lease->ip_addr.iabuf, &cmd.ip.s_addr, 4); + (void)atomicio(vwrite, pfpipe[1], &cmd, sizeof(struct pf_cmd)); + } + return; } /* Locate the lease associated with a given IP address... */ diff --git a/usr.sbin/dhcpd/pfutils.c b/usr.sbin/dhcpd/pfutils.c new file mode 100644 index 00000000000..7d901f5fce6 --- /dev/null +++ b/usr.sbin/dhcpd/pfutils.c @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2006 Chris Kuethe <ckuethe@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/pfvar.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "dhcpd.h" + +extern struct passwd *pw; +extern int pfpipe[2]; +extern char *abandoned_tab; +extern char *changedmac_tab; + +__dead void +pftable_handler() +{ + struct pf_cmd cmd; + struct pollfd pfd[1]; + int l, r, fd, nfds; + + if ((fd = open(_PATH_DEV_PF, O_RDWR|O_NOFOLLOW, 0660)) == -1) + error("can't open pf device: %m"); + if (chroot(_PATH_VAREMPTY) == -1) + error("chroot %s: %m", _PATH_VAREMPTY); + if (chdir("/") == -1) + error("chdir(\"/\"): %m"); + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + error("can't drop privileges: %m"); + + setproctitle("pf table handler"); + l = sizeof(struct pf_cmd); + + for(;;){ + pfd[0].fd = fd; + pfd[0].events = POLLIN; + if ((nfds = poll(pfd, 1, -1)) == -1) + if (errno != EINTR) + error("poll: %m"); + + if (nfds > 0 && (pfd[0].revents & POLLIN)){ + bzero(&cmd, l); + r = atomicio(read, pfpipe[0], &cmd, l); + + if (r != l) + error("pf pipe error: %m"); + + switch (cmd.type){ + case 'A': + pf_change_table(fd, 1, cmd.ip, abandoned_tab); + pf_kill_state(fd, cmd.ip); + break; + case 'C': + pf_change_table(fd, 0, cmd.ip, abandoned_tab); + pf_change_table(fd, 0, cmd.ip, changedmac_tab); + break; + case 'L': + pf_change_table(fd, 0, cmd.ip, abandoned_tab); + break; + default: + break; + } + } + } + /* not reached */ + exit(1); +} + +/* inspired by ("stolen") from usr.sbin/authpf/authpf.c */ +void +pf_change_table(int fd, int op, struct in_addr ip, char *table) +{ + struct pfioc_table io; + struct pfr_addr addr; + + bzero(&io, sizeof(io)); + strlcpy(io.pfrio_table.pfrt_name, table, + sizeof(io.pfrio_table.pfrt_name)); + io.pfrio_buffer = &addr; + io.pfrio_esize = sizeof(addr); + io.pfrio_size = 1; + + bzero(&addr, sizeof(addr)); + bcopy(&ip, &addr.pfra_ip4addr, 4); + addr.pfra_af = AF_INET; + addr.pfra_net = 32; + + if (ioctl(fd, op ? DIOCRADDADDRS : DIOCRDELADDRS, &io) && + errno != ESRCH) { + warning( "DIOCR%sADDRS on table %s: %s", + op ? "ADD" : "DEL", table, strerror(errno)); + } +} + +void +pf_kill_state(int fd, struct in_addr ip) +{ + struct pfioc_state_kill psk; + struct pf_addr target; + + bzero(&psk, sizeof(psk)); + bzero(&target, sizeof(target)); + + bcopy(&ip.s_addr, &target.v4, 4); + psk.psk_af = AF_INET; + + /* Kill all states from target */ + bcopy(&target, &psk.psk_src.addr.v.a.addr, + sizeof(psk.psk_src.addr.v.a.addr)); + memset(&psk.psk_src.addr.v.a.mask, 0xff, + sizeof(psk.psk_src.addr.v.a.mask)); + if (ioctl(fd, DIOCKILLSTATES, &psk)){ + warning("DIOCKILLSTATES failed (%s)", strerror(errno)); + } + + /* Kill all states to target */ + bzero(&psk.psk_src, sizeof(psk.psk_src)); + bcopy(&target, &psk.psk_dst.addr.v.a.addr, + sizeof(psk.psk_dst.addr.v.a.addr)); + memset(&psk.psk_dst.addr.v.a.mask, 0xff, + sizeof(psk.psk_dst.addr.v.a.mask)); + if (ioctl(fd, DIOCKILLSTATES, &psk)){ + warning("DIOCKILLSTATES failed (%s)", strerror(errno)); + } +} + +/* inspired by ("stolen") from usr.bin/ssh/atomicio.c */ +size_t +atomicio(ssize_t (*f) (int, void *, size_t), int fd, void *_s, size_t n) +{ + char *s = _s; + size_t pos = 0; + ssize_t res; + + while (n > pos) { + res = (f) (fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + return 0; + case 0: + errno = EPIPE; + return pos; + default: + pos += (size_t)res; + } + } + return (pos); +} |