I just spent some time figuring out how to set up denyhosts on Snow Leopard. I’ve used denyhosts before, but never felt like I had things set up properly for Mac OS. Now I think I have it figured out, so here it is. This is for 10.6, your mileage may vary on earlier versions.
I had three goals – get denyhosts working, get it to start automatically at boot time, and to deal with rotating the logs.
1. Installation
Easiest first – installing denyhosts. Note that you need to be root to do this. Pretty much just follow the directions. These are the three main settings to worry about.
SECURE_LOG = /private/var/log/secure.log
LOCK_FILE = /var/run/denyhosts.pid
DAEMON_LOG = /var/log/denyhosts
Note that you also may need to create the file /etc/hosts.deny:
Using touch will create a zero-length file if it’s not there. It won’t affect the contents if it is there.
2. Log rotation
Mac OS 10.6 uses newsyslog to rotate some log files (I’m not sure why, but apache logs don’t seem to be dealt with by newsyslog). To add your own to the mix, just put a file into /etc/newsyslog.d/ following the format for newsyslog.conf(5). I called mine local.conf
# logfilename [owner:group] mode count size when flags [/pid_file] [sig_num]
/var/log/denyhosts 640 5 * $D0 J
#
The trouble is, this rotated the log just fine, but then denyhosts stopped logging because newsyslog essentially pulls the rug out from under denyhosts by moving the file.
One design difference between newsyslog and logrotate is the way they deal with notifying processes that logs have been rotated. Logrotate uses prerotate and postrotate scripts, which would be ideal for denyhosts. The way you start and stop it is with
daemon-control start
daemon-control stop
daemon-control stop actually sends a SIGTERM to the denyhosts process, but that won’t do any good in the newsyslog config file since once stopped, you need a command line to start it up again. So I decided to tweak the daemon-control script to do this. I replaced the start() function with the one here:
def start(*args):
cmd = "%s --daemon " % DENYHOSTS_BIN
if args: cmd += ' '.join(args)
print "starting DenyHosts: ", cmd
while True:
os.system(cmd)
time.sleep(5)
while True:
pid = getpid()
if pid >= 0:
time.sleep(300)
else:
break
This just keeps daemon-control running rather than letting it exit after it starts denyhosts. The outer loop starts denyhosts running and later restarts it. The inner loop just waits until it sees the pid file go away. That’s a sure sign that denyhosts stopped running, most likely because of the SIGHUP it will get from newsyslog. Now all I needed to do was add the signal info to my /etc/denyhosts.d/local.conf /etc/newsyslog.d/local.conf file:
# logfilename [owner:group] mode count size when flags [/pid_file] [sig_num]
/var/log/denyhosts 640 30 * $D0 BJ /var/run/denyhosts.pid 15
#
I’ve also changed it to keep 30 days of logs, and added the B flag to prevent newsyslog from adding a line to the file saying it’s rotated the logs. Note that I changed the name to daemon-control2 so if I update denyhosts later, my changes don’t get clobbered.
3. Start at boot time
It turns out that modifying daemon-control to never exit is also just the ticket for running it under launchd. Launchd doesn’t work well on scripts that launch daemonized processes. It watches the script and notices that it’s exited, then tries to start it again.
I made a file called /Library/LaunchDaemons/net.hosts.deny.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>net.denyhosts</string>
<key>ProgramArguments</key>
<array>
<string>/usr/share/denyhosts/daemon-control2</string>
<string>start</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>ServiceDescription</key>
<string>Lauch denyhosts</string>
</dict>
</plist>
Get it started with launchctl:
launchctl load /Library/LaunchDaemons/net.hosts.deny.plist
My /etc/hosts.deny has about 8500 hosts in it right now. Many of those are probably from the denyhosts synchronization feature pulling in IP addresses from the central server.
Update 2010-03-26: Added some links and clarified some bits.
Update 2010-06-06: Note that /etc/hosts.deny must be present. denyhosts won’t create it.