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.
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 yesLater 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 sendmail.cf
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" exitMake 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> main(argc,argv) char **argv; int argc; { /* renounce Setuid */ setreuid(geteuid(),geteuid()); execvp(argv[1],argv+1); exit(1); /* should never happen */ }Next, we must add a new mailer to the
sendmail.cf
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 $hAgain, 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 sendmail.cf
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:
domain.com sshsm:myfriendIn this case, mail to
domain.com
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:
user=rsmtp host=myfriend.dyndns.orgThe 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
.