summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/dhcpd/Makefile4
-rw-r--r--usr.sbin/dhcpd/dhcp.c15
-rw-r--r--usr.sbin/dhcpd/dhcpd.841
-rw-r--r--usr.sbin/dhcpd/dhcpd.c44
-rw-r--r--usr.sbin/dhcpd/dhcpd.h19
-rw-r--r--usr.sbin/dhcpd/memory.c33
-rw-r--r--usr.sbin/dhcpd/pfutils.c182
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, &lt, 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);
+}