Tag Archives: kerberos

ISC DHCPd: Dynamic DNS updates against secure Microsoft DNS

UPDATE 2016: I have posted a much simpler way that works with DNS delegations so that you can have your domain controllers maintain the records necessary for their discovery in Microsoft DNS, while all your clients are in a BIND DNS server which can be easily interfaced with ISC DHCPd.

ISC DHCPd is capable of Dynamic DNS updates against servers like BIND that support shared-key authentication or any other server that supports unauthenticated updates (such as BIND or Microsoft DNS with secure updates disabled).

So, what to do if you want to run ISC DHCPd on your Windows network, which is obviously running Microsoft’s DNS server? BIND’s nsupdate tool supports Microsoft’s Kerberos authentication scheme when using the -g flag (the -o flag is only necessary for Windows 2000 Server, but not anymore for Windows Server 2008 R2), and DHCPd supports on commit/release/expiry blocks that let you run scripts upon these events. So here is my script:




export KRB5CCNAME="/tmp/dhcp-dyndns.cc"

keytab can be generated using

$ ktutil

ktutil: addent -password -p dhcpduser@EXAMPLE.COM -k 1 -e aes256-cts-hmac-sha1-96

Password for dhcpduser@EXAMPLE.COM:

ktutil: wkt dhcpduser.keytab

ktutil: quit


name=$(echo $3 | awk -F '.' '{print $1}')

echo "USAGE:"
echo $0 add testhost 00:11:22:33:44:55
echo $0 add "" 00:11:22:44:33:55
echo $0 delete testhost 00:11:22:33:44:55
echo $0 delete "" 00:11:22:44:33:55

if [ "$ip" = "" ]; then
echo "IP missing"
exit 101
if [ "$name" = "" ]; then
#echo "name missing"
#exit 102
name=$(echo $ip | awk -F '.' '{print "dhcp-"$1"-"$2"-"$3"-"$4}')

if [ "$action" = "delete" ]; then
name=$(host $ip | awk '{print $5}' | awk -F '.' '{print $1}')

echo $name | grep NXDOMAIN 2>$1 >/dev/null
if [ "$?" = "0" ]; then
exit 0;

ptr=$(echo $ip | awk -F '.' '{print $4"."$3"."$2"."$1".in-addr.arpa"}')


#export LD_LIBRARY_PATH=/usr/local/krb5-1.7/lib
#export PATH=/usr/local/krb5-1.7/bin:$PATH

klist 2>&1 | grep $realm | grep '/' > /dev/null
if [ "$?" = 1 ]; then
expiration=$(klist | grep $realm | grep '/' | awk -F ' ' '{system ("date -d \""$2"\" +%s")}' | sort | head -n 1)

now=$(date +%s)
if [ "$now" -ge "$expiration" ]; then
echo "Getting new ticket, old one expired $expiration, now is $now"
kinit -F -k -t $keytab $principal


case "$action" in
echo "Setting $name.$domain to $ip on $ns"

oldname=$(host $ip $ns | grep "domain name pointer" | awk '{print $5}' | awk -F '.' '{print $1}')
if [ "$oldname" = "" ]; then
elif [ "$oldname" = "$name" ]; then
echo "Also deleting $oldname A record"

nsupdate -g <
server $ns
realm $realm
update delete $oldname.$domain 3600 A
update delete $name.$domain 3600 A
update add $name.$domain 3600 A $ip
nsupdate -g <
server $ns
realm $realm
update delete $ptr 3600 PTR
update add $ptr 3600 PTR $name.$domain

echo "Deleting $name.$domain to $ip on $ns"
nsupdate -g <
server $ns
realm $realm
update delete $name.$domain 3600 A
nsupdate -g <
server $ns
realm $realm
update delete $ptr 3600 PTR
echo "Invalid action specified"
exit 103

if [ "$result" != "00" ]; then
echo "DHCP-DNS Update failed: $result"
logger "DHCP-DNS Update failed: $result"

exit $result

and here is the relevant part of my dhcpd.conf:

on commit {
set noname = concat("dhcp-", binary-to-ascii(10, 8, "-", leased-address));
set ClientIP = binary-to-ascii(10, 8, ".", leased-address);
set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
set ClientName = pick-first-value(option host-name, host-decl-name, config-option host-name, noname);
log(concat("Commit: IP: ", ClientIP, " Mac: ", ClientMac, " Name: ", ClientName));

execute("/root/dhcp-dyndns.sh", "add", ClientIP, ClientName, ClientMac);
on release {
set ClientIP = binary-to-ascii(10, 8, ".", leased-address);
set ClientMac = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
log(concat("Release: IP: ", ClientIP, " Mac: ", ClientMac));

cannot get a ClientName here, for some reason that always fails

execute("/root/dhcp-dyndns.sh", "delete", ClientIP, "", ClientMac);
on expiry {
set ClientIP = binary-to-ascii(10, 8, ".", leased-address);

cannot get a ClientMac here, apparently this only works when actually receiving a packet

log(concat("Expired: IP: ", ClientIP));

cannot get a ClientName here, for some reason that always fails

execute("/root/dhcp-dyndns.sh", "delete", ClientIP, "", "0");

Figuring this all out took me several afternoons because Kerberos 5 1.8 has a bug where forwardable tickets (which is the default on Debian) are incompatible with nsupdate. Manually compiling 1.7 or getting 1.9 from the experimental Debian branch helps, as does adding the -F flag to kinit (which I did in the script above) to make the ticket non-forwardable.
I filed a bug with Debian (#611906) and Sam Hartman (thanks!) helped me track it down.

EDIT 2011-11-17:
I recently ran into the issue that if the AD server could not be reached, dhcpd would stall (and not respond to DHCP requests during that time) until nsupdate reached its timeout. The fix is simple: rename dhcp-dyndns.sh to dhcp-dyndns-real.sh and create dhcp-dyndns.sh with the following contents to fork off the real script into the background:

$(dirname $0)/dhcp-dyndns.sh $@ 2>&1 | logger &

Also, I updated the on commit section in the dhcpd.conf excerpt above to compose a fallback name from the IP address if the client provides no hostname. This fixes the issue that nsupdate tries to register a record based on the name and fails.

Building your own OpenDirectory server on Linux

OpenDirectory is a feature included with Mac OS X Server. Wouldn’t it be nice if you could use it without having to spend hundreds of dollars on a server license? Wouldn’t it be great if you could add it into your existing Linux-based OpenLDAP server? It’s actually quite easy because OpenDirectory is a standard OpenLDAP server with a special Apple schema.

0. Prerequisites
– OpenLDAP server with Samba integration (I’m runnig it on a Ubuntu 8.04 server, using the standard OpenLDAP and Samba packages). I won’t go into the details of how to set this up, there are lots of tutorials around the web on this.
– some kind of LDAP admin tool, I used phpLDAPAdmin
– Mac OS X 10.5 Leopard clients

1. Adding the Apple schema to your OpenDirectory server
It is located in /etc/openldap/schema/apple.schema on any Mac. Copy this file to your OpenLDAP server and add it to your slapd.conf.
You may run into the problem that apple.schema references some samba.schema entries that were deprecated with Samba 3. Specifically, these are acctFlags, pwdLastSet, logonTime, logoffTime, kickoffTime, homeDrive, scriptPath, profilePath, userWorkstations, smbHome, rid and primaryGroupID, so you’ll need to editapple.schema and replace these with their Samba 3 counterparts.
Now, restart the OpenLDAP daemon so it recognizes the changes.

2. Adding some Mac-specific attributes to your LDAP server
Add an ou=macosx branch to your LDAP tree, under which you’ll need to create ou=accesscontrols, ou=augments, ou=automountMap, ou=autoserversetup, ou=certificateauthorities, ou=computer_groups, ou=computer_lists, ou=computers, ou=filemakerservers, ou=locations, ou=machines, ou=maps, ou=mount, ou=neighborhoods, ou=places, ou=preset_computer_groups, ou=preset_computer_lists, ou=preset_computers, ou=preset_groups, ou=preset_users, ou=printers, and ou=resources.
To all your LDAP groups, add the apple-group objectClass. To all your LDAP users, add the apple-user objectClass.

3. Connecting your Mac to the LDAP directory
On your Mac, go into Directory Access and add your LDAP server. Choose OpenDirectory as the server type and adjust the Samba mappings to match your changes from step 1. Here is a plist you can import into Directory Access that already has these mappings corrected: LDAPv3_Unix_Samba3_OD.plist.
If you want your other clients to automatically use this mapping, create a cn=config branch in your LDAP tree and use the Write to Server button in Directory Access.

4. Use Workgroup Manager to set network home folders, managed preferences, …
Now, you can use Workgroup Manager to manage network home folders and managed preferences, just like you would on a Mac server.  You’ll need to authenticate using an LDAP user who has full write privileges to the directory (as set in slapd.conf). The standard cn=admin,dc=example,dc=com user will NOT work.

5. Conclusion
Almost everything works, except for:
– adding new users and group through Workgroup Manager
– solution: unknown
– assigning directory admin privileges to users through Workgroup Manager
– solution: using an OpenLDAP server set up to use cn=config instead of slapd.conf. This will also require going into Directory Access again and adding the OLCBDBConfig, OLCFrontEndConfig, OCGlobalConfig, OLCSchemaConfig and OLCOverlayDynamicID record types back in (they are included in the OpenDirectory mapping, but I deleted them from mine because they only cause error messages on an OpenLDAP server with slapd.conf configuration).

Here are all the web sites that helped me in the process of figuring this out:
http://docs.info.apple.com/article.html?path=ServerAdmin/10.4/en/c6od15.html (this one is especially important because it explains what to do if your LDAP server is not set up for SASL authentication)
http://www.emmes-world.de/mac-afp-homes.html (this one describes a similar setup and was my most important resource)

7. Further Information
Since you’re not using Kerberos for authentication, you may want to look at securing your LDAP connections with SSL. Here are some links that talk about it:

Someone else also wrote a blog post about Setting up a Linux server for OS X clients, in which they also describe how to incorporate Kerberos into the whole equation. That’s certainly something worth considering.