SSH Tricks

My shell scripting skills suck. So it comes naturally that I learn a lot every time I need to write a script. The trick I’m about to describe is so neat that I want to share it.

Assume for a second that you need to execute a series of commands on a remote machine, but you can’t pass them to SSH at once. Or consider that you might need to transfer a file over to a remote host and then extract it there. Normally, you’d need to create several SSH connections for this. In the “transfer, then extract” scenario, you need one connection for scp(1) and another one to extract the file on the remote host. Unless you have your public key in the remote host’s ~/.ssh/authorized_keys file, you need to enter your password for the remote host twice. It’s probably not a big deal for most, but it’s annoying at times.

It might be obvious for some, but I recently found out that ssh(1) offers a solution for the problem described above. It allows you to open one connection in "master" mode to which other SSH processes can connect through a socket. The options for the "master" connection are:

$ ssh -M -S /path/to/socket user@rhost

Alternatively, the ControlPath and ControlMaster options can be set in the appropriate SSH configuration files. Other SSH processes that want to connect to the "master" connection only need to use the -S option. The authentication of the "master" connection will be re-used for all other connections that go through the socket. I’m not sure if SSH even opens a separate TCP connection.

Going further, this can be used in scripts to save the user a lot of password typing, especially if the execution flow switches between local and remote commands a lot. At the beginning of the script, simply create a &qout;master" connection like this:

$ ssh -M -S /path/to/socket -f user@rhost 
    "while true ; do sleep 3600 ; done"

It should be noted that the path to the socket can be made unique by using a combination of the placeholders %l, %h, %p, %r for the local host, remote host, port and remote username, respectively. The -f parameter makes SSH go into the background just before command execution. However, it requires that a command is specified, hence an endless loop of sleep(1) calls is added to the command line. Other SSH connections can be opened like this, not requiring any further user input (for authentication):

$ ssh -S /path/to/socket user@rhost

This leaves the problem of how the "master" process can be terminated when the script has finished. Some versions of SSH offer the -O parameter which can be used to check if the "master" is still running and, more importantly, to tell the "master" to exit. Note that in addition to the socket, the remote user and host need to be specified.

$ ssh -S /path/to/socket -O check user@rhost
$ ssh -S /path/to/socket -O exit user@rhost

However, there are still two problems to be solved. First, when the "master" quits, the dummy sleep loop continues to run. And second, how can the "master" be terminated if the given SSH version doesn’t offer the -O parameter (short of killing the process by its PID)?

DHCP, DNS and dynamic updates

Today I updated my router, a Soekris net4801 (I think), to OpenBSD 4.2. I know it’s dumb to upgrade to OpenBSD 4.2 about three weeks before OpenBSD 4.3 is officially released, but today I actually had time and the box was in desperate need of an update. Also, I have recently moved and the network changed. I used to have a designated server running FreeBSD that also handled DNS and DHCP. When I moved, I shut the server down, and so for the last few weeks I had only very basic DHCP services running and no local DNS at all. Anyways, to make the long story short, I needed a DNS server that would resolve local names as well as a DHCP server that does dynamic DNS upates.

First, I installed OpenBSD 4.2 by netbooting the Soekris box from another OpenBSD “box” running inside Parallels. The instructions for that can be found in the OpenBSD FAQ. There is one thing to remember, though: The Soekris doesn’t have a VGA console but only serial, so the PXE-booted kernel needs to be told that it should only use the serial console for output. So the boot.conf file the FAQ mentions needs to look like this:

set tty com0
set stty 9600
boot bsd.rd

Now on to the DHCP and DNS installation. The DHCP server included with OpenBSD will not do dynamic DNS updates, so the ISC DHCP Server is needed. It can be installed by running (as root):

# ftp ftp://ftp.de.openbsd.org/pub/OpenBSD/4.2/packages/i386/isc-dhcp-server-3.0.4p0.tgz
# pkg_add -v isc-dhcp-server-3.0.4p0.tgz

The dhcpd binary will be installed in /usr/local/sbin. Be aware that the base dhcpd included in OpenBSD lives in /usr/sbin, so simply typing “dhcpd” at the command line will most certainly start the OpenBSD DHCP Server! In order to automatically start the DHCP server on boot, these lines need to be added to /etc/rc.local:

if [ -x /usr/local/sbin/dhcpd ] ; then
        echo -n ' dhcpd' ; /usr/local/sbin/dhcpd
fi

Next, before the Server can be fully configured, a key that will be shared between DHCP and DNS server needs to be created:

$ dnssec-keygen -a HMAC-MD5 -b 128 -n USER <name>

This command generates to files in the current working directory. The file with the extension *.private will include the key required later. Wherever the configuration files include a “secret” statement, that value needs to be inserted. The parameter <name> determines the name of the key. This will be used later, but I don’t know if the name actually has to be reused. For the rest of this posting, we’ll use key_name to represent the generated key’s name.

So now on to the DHCP Server configuration. My /etc/dhcpd.conf now looks like this:

option  domain-name "local.deadc0.de";

ddns-update-style ad-hoc;

key key_name {
        algorithm       hmac-md5;
        secret          "...";
}

zone local.deadc0.de. {
        primary 127.0.0.1;
        key key_name;
}

zone 1.168.192.in-addr.arpa. {
        primary 127.0.0.1;
        key key_name;
}

zone 2.168.192.in-addr.arpa. {
        primary 127.0.0.1;
        key key_name;
}

subnet 192.168.1.0 netmask 255.255.255.0 {
        range 192.168.1.1 192.168.1.127;
        
        option domain-name-servers 192.168.1.254;
        option routers 192.168.1.254;
}
        
subnet 192.168.2.0 netmask 255.255.255.0 {
        range 192.168.2.1 192.168.2.127;

        option domain-name-servers 192.168.2.254;
        option routers 192.168.2.254;
}

Now that the DHCP server is configured, the DNS server needs configuration. In OpenBSD, the DNS Server is BIND, but it’s started in a chroot environment, so its configuration files live under /var/named. The server configuration file is /var/named/etc/named.conf and looks like this on my system:

include "etc/rndc.key";

controls {
        inet 127.0.0.1 allow {
                localhost;
        } keys {
                key_name;
        };
};

acl clients {
        localnets;
        ::1;
};

options {
        listen-on    { any; };
        listen-on-v6 { any; };

        allow-recursion { clients; };
};

// Standard zones ommited.

zone "local.deadc0.de" {
        type master;
        file "master/local.deadc0.de";
        
        allow-update {
                key     "key_name";
        };
};

zone "1.168.192.in-addr.arpa" {
        type master;
        file "master/1.168.192.in-addr.arpa";

        allow-update {
                key     "key_name";
        };
};

zone "2.168.192.in-addr.arpa" {
        type master;
        file "master/2.168.192.in-addr.arpa";

        allow-update {
                key     "key_name";
        };
};

The actual zone files will not be posted, they are just standard zone declarations, nothing special. However, notice the include statement at top of the file. It includes the key declaration file /var/named/etc/rndc.key that looks like this:

key key_name {
        algorithm hmac-md5;
        secret "...";
};

In order to supress some warning when the DNS server starts, the file /var/named/etc/rndc.conf needs to be created. It should look like this:

options {
        default-server  localhost;
        default-key     "key_name";
};

server localhost {
        key     "key_name";
};

include "rndc.key";

Finally, everything under /var/named/etc and /var/named/master needs to be owned by the user “named”, so as root run this:

# chown -R named:named /var/named/etc
# chown -R named:named /var/named/master

Now make sure that the DNS server is enabled by including this line in /etc/rc.conf.local:

named_flags=""

Then reboot the box and that should be it.