MX hosting for dynamic clients

A friend of mine recently was able to get the el-cheapo Pac$Bell DSL deal. Alas, one of the conditions is that he has a dynamic IP address. With the advent of Dynamic DNS domains, this is much less of an inconvenience than it once was. Unfortunately, there is some latency between his IP address changing (it changes because he uses PPPoE and occasionally the PPP connection is lost and must "redial") and the notification going to the DNS folks. If there was anyone else running an open SMTP port in this same dynamic pool space, it is possible that his mail could be misdelivered. Sendmail does not check that the SMTP host you connect to is the right one, after all.

The fix for this is to tunnel SMTP through SSH. SSH is capable of validating the identity of the remote host (as well, of course, as the credentials of the connecting user/daemon), and it's not terribly difficult to get sendmail to use an alternate transport for an SMTP session.

I tested this with FreeBSD 4.0-RELEASE, which includes openssh. To get ssh to work, the rsaref port is needed, but no other software was required. As a byproduct of this method, my friend's firewall need not admit connections to his SMTP port, since his mail will arrive solely via this forwarding method.

How to do it

On the server machine, make a new user. This user must have a home directory for storage of the .ssh directory, but it need not have a shell or a usable password (use '*' or some such).

Run ssh-keygen to make a keypair for this user. Be sure that the permissions are set correctly!

Create a .ssh/config file for this user. Start it out with:

host *
	CheckHostIp no
	BatchMode yes
Later on, we will add StrictHostKeyChecking yes, but you can't do that until you've gotten the host key of the client into your known_host file.

On the client machine, make a new user. This user too must have a home directory for storage of the .ssh directory, but it must have /bin/sh as a shell, since it will be used to dispatch a command from sshd. It should have its password 'starred out' to prevent it being used by anything else.

In the client's file, add the line Tuser, where user is the user you created in the previous step. This user will be allowed to set the envelope from field of messages routinely, and this will prevent warnings.

Copy the public key from the server to the authorized_keys file of the client. Prepend the text command="/usr/sbin/sendmail -bs" to the front of the line containing the key. This will insure that the key in question can only ever be used to deliver mail.

At this point, you should be able to manually ssh to the client machine using the -I argument to specify the private key of the server's special user. You will at this point either wish to tell ssh that it is OK to trust the host key or verify it or some such. After this step is done, it is ok to add StrictHostKeyChecking yes to the .ssh file of the special user on the server side. This will positively insure that the mail will always be delivered to the correct host.

At this point the client side is completed. It's time to finish up the server side. To do so, we will need a small shell script which I call sshsm and a helper C program called rsuid. sshsm will take an argument which will specify the name of a configuration file. This argument will come from sendmail (more about that later). The configuration file will determine the remote host and username to use in the ssh connection. Here's sshsm:

#! /bin/sh

cd /etc/mail/bsmtp

# Read in config file
. $1.conf

# rsuid = Renounce SetUID
exec ./rsuid /usr/bin/ssh -l "$user" "$host" exit
Make sure to change the directory in the cd command as appropriate. Note that the command given to ssh is exit. The command= clause in the client's authorized key file will override this, so it can be anything. Specifying something that wouldn't otherwise work insures that the security setup is working correctly (to some extent).

rsuid is a program that sets the real uid of the process to be the same as the effective uid. This is necessary because sendmail will set the euid of the process to our specifications, but is itself suid. ssh sees the real uid and misinterprets what it should do, so we must renounce our real uid before we call ssh. here's rsuid.c:

#include <stdlib.h>
#include <stdio.h>

char **argv;
int argc;
        /* renounce Setuid */
        exit(1); /* should never happen */
Next, we must add a new mailer to the file:
Msshsm,         P=sshsm, F=SmDFMuX8, S=11/31, R=21, E=\r\n,
                L=990, T=X-Special-SMTP-Transport, U=bsmtp, D=/etc/mail/bsmtp,
                A=sshsm $h       
Again, change the D= parameter to match what you have set up. This mailer is a copy of the Msmtp mailer with the following changes: The addition of the D=, A= and P= parameters, and the addition of the S to the F= one. The S flag insures that the sshsm script will always be called by the user specified in the U= parameter (which should be the special server-side user). After modifying the file, be sure to restart sendmail (a SIGHUP will do).

The last thing we must do is instruct sendmail to use this mailer as necessary for the destination e-mail address(es) in question. The mailertable is the ideal way to do this. An entry like this will do:	sshsm:myfriend
In this case, mail to will be handled by the sshsm script with myfriend as the argument.

This brings us to the sshsm configuration file. Make a myfriend.conf file like this:

The user is the username to be used on the client end of the connection, and the host is the hostname to use to make the connection.

That's all there is to it! The config files allow you to set up multiple clients and select which client via the mailertable file. Just pass out the server's public key to each client and set them up with that in the authorized key file with a command of sendmail -bs. Then add the appropriate line to the known_hosts file and ssh will cryptographically insure that the mail gets to the right place. This technique makes other SMTP tunneling possible, such as tunneling mail from a DMZ host to an inside machine. Just be sure that the keypair located on the DMZ machine does not have rights to do anything but invoke sendmail -bs.