Re: [Exim] SMTP Auth against /etc/master.passwd on FreeBSD

Top Page
Delete this message
Reply to this message
Author: Jez Hancock
Date:  
To: exim-users
Subject: Re: [Exim] SMTP Auth against /etc/master.passwd on FreeBSD
Hi all,

This is a summary of one solution to the subject problem which was put forward by Kirill Miazine
and hacked at to do MD5 authentication against the system password file /etc/master.passwd
on FreeBSD by.

The following perl code uses the Net::Server module to fork a specified number of server
processes (similar in manner to the mechanism employed by apache webserver), each process
listening for incoming authentication traffic on a unix domain socket.

Upon receiving an authentication request on a listening socket,
the server does some minimal data validation checking on the submitted user/password pair.

The server then retrieves the pwd hash from /etc/master.passwd based on the 'username' key
submitted and then hashes the submitted 'password' string using the salt obtained from
the pwd hash. If the resulting hash matches the pwd hash, the user is authenticated.

The script has to be suid root with suidperl unfortunately since it has to read
/etc/master.passwd. However this entails that the domain sockets created by the server
are not readable by the exim user (unless you like running exim as root:) It's conceivable
to have the server not suid and instead have it call a suid root helper that makes the lookup
on the db, but to be honest I'm happy now I've got pwcheck back and won't go to the effort.

The code is here anyway in case anyone in future is interested:

-----------------------------------------------------------------------------
#!/usr/bin/perl

package EximAuth;

use strict;
use vars qw(@ISA);
use Crypt::PasswdMD5;
use Net::Server::PreFork;
@ISA = qw(Net::Server::PreFork);

sub process_request {
    my $self = shift;
    my $sock = $self->{'server'}->{'client'};
    chomp(my $username = $sock->getline());
    chomp(my $password = $sock->getline());


    # some minimal data validation:
    !$username ||
    !$password ||
    length($username) >16 ||
    length($password) >255 ||
    $username=~/[^0-9A-Za-z]/ ||
    $password=~/[^0-9A-Za-z]/ &&
    $sock->print("no");


    # get the line from the pwd db based on $username:
    my (undef,$sys_password) = getpwnam($username);


    # get the salt:
    my ($salt) = $sys_password =~ /\$.*\$(.*)\$/;


    # get our hash:
    my $hash=unix_md5_crypt($password, $salt);


    $sock->print($hash eq $sys_password ? 'yes' : 'no');
    # for debug output:
    # $sock->print("username: $username submitted password: "
        ."$password salt: $salt myhash: $hash syshash: $sys_password\n");
}


__PACKAGE__->run(
    proto => 'unix',
    port => '/var/spool/exim/auth/auth.sock',
    min_servers => 5,
    min_spare_servers => 5,
    max_spare_servers => 10,
    max_servers => 15,
    max_requests => 1000,
    log_level => 4,
);
------------------------------------------------------------------------------
and an exim authenticator:
------------------------------------------------------------------------------
plain:
  driver = plaintext
  public_name = PLAIN
  server_condition = ${readsocket{/var/spool/exim/auth/auth.sock}{$2\n$3\n}}
------------------------------------------------------------------------------


Again, most of the credit for this idea should go to Kirill Miazine - thanks for that :)

Regards,
Jez