Articles tagged: code

Mobile LAN-oriented filtering in iptables

One of the things that I really like about pf, the OpenBSD firewall, is how it lets you define dynamic packet filtering rules — rules that filter based on your network interfaces’ current addresses at the time of filtering. For instance, if I want to allow SSH connections to my laptop only from my local network:

pass in on xl0 inet proto tcp from (xl0:network) to any \
port ssh flags S/SFRA

(xl0:network) is not resolved to a specific address block at configuration load time; if you switch networks — say, if you go from home to work — the rule’s behavior will change accordingly.

Unless I have overlooked some recent change in Linux, this cannot be achieved in a direct fashion with iptables. You can insert a rule to reject non-LAN source addresses, but such a rule is static. When you change network addresses, the rule must be explicitly updated.

In lieu of rewriting all of netfilter to accommodate this use case (*cough*), I just wrote a shell script to help mitigate the pain of manually updating my laptop’s firewall rules — merely a shortcut to cut down on the amount of typing I do on any given day, but if you tend to move around as much as I do, all those keystrokes can add up :) So with this script you can, in one fell swoop, start and open up global access to an SSH server:

# ssh-serve any

Or only allow local access from the networks you’re connected to:

# ssh-serve lan

Or only local network access on a specific interface:

# ssh-serve lan eth0

Or only access from a given set of IP addresses and/or CIDR blocks:

# ssh-serve addr 192.168.0.104 10.18.0.0/16

Better yet, you can make the whole process automagical by hooking into your Linux distribution’s DHCP client. For instance, in Ubuntu Hardy Heron you can automate ssh-serve by creating a file /etc/dhcp3/dhclient-exit-hooks.d/ssh-serve:

# Allow SSH access from your local network only, and keep these filter
# rules up-to-date as you move from one network to another.
case $reason in
BOUND|REBIND|REBOOT)
ssh-serve lan
;;
esac

This script was written (and named) with Secure Shell in mind, but it could just as easily govern over any other service controlled by a standard SysV init script. See below the jump for the code…

#!/bin/sh
# ssh-serve - Manage SSH server status and IP-based access control.
#
# Use this script to manage the state of the system's OpenSSH server, and
# the iptables rules allowing or denying remote access to it, in one fell
# swoop.  Synopsis:
#
# ssh-serve any
#   Start the server and allow access from anywhere.
#
# ssh-serve lan ( <iface-name> )*
#   Start the server and restrict access to clients connecting from from
#   local network addresses on one of any number of the computer's network
#   interfaces.  If no interface names are specified, then all non-loopback
#   interfaces listed by ifconfig will be provisioned for.
#
# ssh-serve addr ( <cidr> )+
#   Start the server and restrict access to clients connecting from one of
#   any number of specified IP addresses or CIDR blocks.
#
# ssh-serve off
#   Shut down the SSH server and close off access in the firewall.
#
# In order to use this, you will need to set up a separate iptables INPUT
# chain to which this script has exclusive write access; it will overwrite
# any other rules that may exist in its chain.  For example, you could do
# the following:
#
# $ sudo iptables -N SSH_ACTION
# $ sudo iptables -A SSH_ACTION -j REJECT
# $ sudo iptables -A INPUT -t tcp --dport 22 -m state --state NEW \
#   -j SSH_ACTION
#
# Then set the variable SSH_CHAIN to 'SSH_ACTION' in the configuration
# section below.
#
# Mark Shroyer
# Tue Sep 23 17:11:25 EDT 2008
### BEGIN CONFIGURATION ###################################################
# SSH connection logic iptables chain.  Only new, TCP port 22 connections
# in the INPUT table should be jumped to this chain.  WARNING: Any
# pre-existing rules in this chain will be overwritten by this script.
SSH_CHAIN=SSH_ACTION
# Jump target for accepted connections (typically ACCEPT)
JUMP_ACCEPT=SSH_ACCEPT
# Jump target for rejected connections (typically DROP or REJECT)
JUMP_REJECT=REJECT
# Where is our ssh init script?
SSH_INIT_SCRIPT=/etc/init.d/ssh
# Where is our iptables?
IPTABLES=iptables
# Where is our ifconfig?
IFCONFIG=ifconfig
### END CONFIGURATION #####################################################
run() {
echo $@
$@
}
usage() {
echo "Usage: $0 ( any | lan (<iface-name>)* | addr (<cidr>)+ | off )"
exit 1
}
mask_to_cidr() {
mask=$( echo "$1" | tr . ' ' )
sum=0
error=0
for part in $mask
do
case $part in
255) sum=$(( $sum+8 )) ;;
254) sum=$(( $sum+7 )) ;;
252) sum=$(( $sum+6 )) ;;
248) sum=$(( $sum+5 )) ;;
240) sum=$(( $sum+4 )) ;;
224) sum=$(( $sum+3 )) ;;
192) sum=$(( $sum+2 )) ;;
128) sum=$(( $sum+1 )) ;;
0) sum=$(( $sum )) ;;
*) error=1 ;;
esac
done
if [ $error -eq 0 ]
then
echo -n $sum
else
return 1
fi
}
ssh_serve_off() {
run $SSH_INIT_SCRIPT stop
run $IPTABLES -F $SSH_CHAIN
run $IPTABLES -A $SSH_CHAIN -j $JUMP_REJECT
}
ssh_serve_lan() {
ifaces=$( $IFCONFIG | awk '
/^[a-zA-Z0-9]+/ {
if ( $1 !~ /^lo[0-9]*$/ ) {
printf "%s ", $1;
}
}
' )
ifaddrs=''
for iface in $ifaces
do
info=$( $IFCONFIG $iface | awk '
/inet addr:/    { info=$0; }
/UP/            { up=1; }
/RUNNING/       { running=1; }
END             {
if ( up && running ) {
printf info;
}
}
' )
if [ $# -gt 1 ]
then
if ! echo " $@ " | grep -q " $iface "
then
continue
fi
fi
if [ ! -z "$info" ]
then
vals=$( echo "${info}" \
| sed -e 's/Bcast:[^\ ]\+//g' -e 's/[a-zA-Z]\+:\?//g' )
addr=$( echo "${vals}" | awk '{ printf "%s", $1; }' )
mask=$( echo "${vals}" | awk '{ printf "%s", $2; }' )
fi
if [ \( ! -z "$addr" \) -a \( ! -z "$mask" \) -a \( ! -z "$info" \) ]
then
cidr=$( mask_to_cidr $mask )
if [ $? -ne 0 ]
then
echo "CIDR conversion error on netmask ${mask}.  Aborting."
exit -1
fi
ifaddrs="${ifaddrs}${iface}:${addr}/${cidr} "
fi
done
run $IPTABLES -F $SSH_CHAIN
for ifaddr in $ifaddrs
do
iface=$( echo ${ifaddr} | awk 'BEGIN { FS=":"; } { printf "%s", $1 }' )
addr=$( echo ${ifaddr} | awk 'BEGIN { FS=":"; } { printf "%s", $2 }' )
run $IPTABLES -A $SSH_CHAIN -i $iface -s $addr -j $JUMP_ACCEPT
done
run $IPTABLES -A $SSH_CHAIN -j $JUMP_REJECT
run $SSH_INIT_SCRIPT start
}
ssh_serve_addr() {
if [ $# -lt 1 ]
then
usage
fi
run $IPTABLES -F $SSH_CHAIN
for addr in "$@"
do
run $IPTABLES -A $SSH_CHAIN -s "$addr" -j $JUMP_ACCEPT
done
run $IPTABLES -A $SSH_CHAIN -j $JUMP_REJECT
run $SSH_INIT_SCRIPT start
}
ssh_serve_any() {
run $IPTABLES -F $SSH_CHAIN
run $IPTABLES -A $SSH_CHAIN -j $JUMP_ACCEPT
run $SSH_INIT_SCRIPT start
}
command="$1"
if [ $# -gt 0 ]
then
shift
fi
case "$command" in
off|lan|addr|any)
ssh_serve_${command} $@
;;
*)
usage
;;
esac

Patch for segfault in OpenBSD 4.3’s pfctl

A couple of months ago, I upgraded an old PowerPC machine from OpenBSD 4.2 to 4.3, and I discovered that the new version of pfctl in 4.3 would segfault when reading my old pf.conf file. Some brief poking around with GDB revealed the root of the problem, an uninitialized variable in the new configuration file parser.

If you’ve been bitten by this as well, here’s a patch with the minor change that solved the problem for me:

--- sbin/pfctl/parse.y  Sat Feb 23 15:31:08 2008
+++ sbin/pfctl/parse.y  Thu May 15 08:55:38 2008
@@ -3487,9 +3487,11 @@
qname          : QUEUE STRING                          {
$$.qname = $2;
+                       $$.pqname = NULL;
}
| QUEUE '(' STRING ')'                  {
$$.qname = $3;
+                       $$.pqname = NULL;
}
| QUEUE '(' STRING comma STRING ')'     {
$$.qname = $3;

To apply this patch, perform the following (assuming that you have the OpenBSD 4.3 source code tree at /usr/src on your system):

# cd /usr/src
# patch -p0 </path/to/above/patch
# cd sbin/pfctl
# make && make install

My ISP blocks outbound SMTP traffic, unfortunately, and I didn’t feel like setting up Sendmail relaying just so I could submit a sendbug report, so I couldn’t open a ticket for the bug. I did send this patch to the bugs@ mailing list, but it was unable to generate any interest there; if someone stumbles across this who has a functional sendbug on their system, I’d be grateful if you could submit this patch in a proper bug report.

The segmentation fault doesn’t occur on the i386 port of OpenBSD (as far as I can tell), nor does it occur on the macppc port unless you use the “queue ( qname, pqname )ALTQ syntax, so it’s easy to see why the hordes aren’t exactly beating down the OpenBSD folks’ doors about this one. So I figured I should post this here, where people might find it, until someone gets around to committing an official fix.

UF Weather Report Widget Thingy

I’ve tired of manually pointing my browser at http://www.phys.ufl.edu/weather/ each time I want to check the conditions on campus, so I made a small widget to bring the campus weather report to my NetVibes home page:

UF Weather widget screenshot

Fellow Gators can use this widget too – just click here to add it to your NetVibes home page, or here to add it to iGoogle. Or copy the widget’s URL to manually add it to any other UWA-aware application:

http://markshroyer.com/code/uwa/uf-weather/widget.xhtml

ISC DHCPD status report generator

After setting up my apartment’s OpenBSD based network, I decided it would be handy to have a simple report generator to describe active DHCPD leases. I also wanted to brush up on Perl a bit. This Perl script is the natural result of such circumstances.

Run dhcpd-report.pl to generate a plaintext or HTML report of current leases in the DHCPD lease database and reservations in dhcpd.conf. In my usage, I have the script set to run once every five minutes in a cron job, sending output to an HTML file in a web server document root. (On OpenBSD, httpd runs in a chroot jail; if I were to run this script as CGI it wouldn’t have direct access to the DHCPD configuration files.) Thus the script can be used to give you a reasonably up to date web page displaying your network’s DHCP clients.

By default, the program looks for dhcpd.leases and dhcpd.conf in /var/db/ and /etc/, respectively, which are these files’ locations in OpenBSD 4.1. If needed, you can change where the script looks for these files by modifying a pair of constants near the top of the main program.

dhcpd-report.pl requires the Date::Format and Date::Parse modules from CPAN. See the POD documentation* for more info.

Download: dhcpd-report.pl


*Sort of like an ATM machine or a PIN number…

Exporting AddressBook birthdays to Remind

I always thought I was a bit of an oddball for choosing the ancient scheduler Remind as my favorite OS X calendar software. But as it turns out, I’m not quite as alone as I had assumed. I guess OS X is a bigger draw to Unix geeks than I realized.

So I’ve decided to publish a tool that I wrote a few months back, a simple little Objective-C command line application that reads the birthdays of all your OS X AddressBook contacts and outputs them in .reminders format.

The source code bundle is here. After installation, see the manpage remind_birthdays(1) for documentation.

You can also view the source code repository on Github.

Pagination