Jez Hancock wrote:
> Hi,
>
> Ok thanks for the repsonses so far - especially from William Thompson who was kind enough
> to provide some code for authenticating against a linux shadow password file.
>
> Unfortunately I'm no C coder and so porting this to freebsd is out. Yes I know I should learn
> C, but I'm short on time as always :)
>
> Anyway, I've come up with the following perl script which I'm including into exim as follows:
>
> perl_startup = do '/usr/local/etc/exim/exim.pl'
>
> '/usr/local/etc/exim/exim.pl' is as follows:
> ------------------------------------------------------------------------------
> #!/usr/bin/perl
> use strict;
> use Crypt::PasswdMD5;
>
> =comment
> Note this perl script should be SUID 0 and be placed in your exim config dir.
This is wrong. The script will not be run, the code will be soaked into
Exim and then it will be executed as the user Exim runs as.
>
> It should be included into exim via the exim configure file with a line like:
>
> perl_startup = do '/usr/local/etc/exim/exim.pl'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
That's why making the script setuid root will not help.
> The relevant subroutines can then be called from the exim configure file
> using:
>
> ${perl{foo}}
>
> where 'foo' is a subroutine defined within this script.
> =cut
>
> This perl script works ok in a shell - for example on a dummy account
> I setup for testing the output looks something like this:
>
> [11:41:18] root@users /usr/local/etc/exim# ./exim.pl dummy dummy
You invoke it as root. That's why it works. Try running it as a normal
user and it will fail (unless you have suidperl enabled). Setuid scripts
are a bad idea in general IMHO. Create a small wrapper that calls your
script, make the wrapper setuid and you're done. But that's another
story - exim.pl is not executed, its code more or less "included".
> which corresponds to the entry for 'dummy' in /etc/master.passwd:
>
> [11:41:53] root@users /usr/local/etc/exim# grep ^dummy: /etc/master.passwd
> dummy:$1$koz3aLbT$j9Srfw60S7U6MnjGN2Met1:1124:1124::0:0:User &:/home/dummy:/sbin/nologin
>
> Now my authenticator in /usr/local/etc/exim/configure looks like this (this is the problem I think!):
>
> ------------------------------------------------------------------------------
> begin authenticators
>
> plain:
> driver = plaintext
> public_name = PLAIN
> server_condition = ${perl{MD5_auth}{$1}{$2}}
> # server_condition = "${if and {{!eq{$2}{}}{!eq{$3}{}} \
> # {crypteq{$3}{${extract{1}{:} \
> #{${lookup{$2}lsearch{/etc/master.passwd}{$value}{*:*}}}}}}}{1}{0}}"
> # server_set_id = $2
>
> login:
> driver = plaintext
> public_name = LOGIN
> server_prompts = "Username:: : Password::"
> server_condition = ${perl{MD5_auth}{$2}{$3}}
> # server_condition = "${if and {{!eq{$1}{}}{!eq{$2}{}} \
> # {crypteq{$2}{${extract{1}{:} \
> #{${lookup{$1}lsearch{/etc/master.passwd}{$value}{*:*}}}}}}}{1}{0}}"
> # server_set_id = $1
> ------------------------------------------------------------------------------
>
> Which is yielding nothing but 'Bad user/pass pair' in my exim mainlog.
>
> Can anyone shed some light on how my authenticator should look. Also
> how can I debug this better. Currently I'm doing this:
Your authenticator looks ok at the first glance - just make sure that
"${perl{MD5_auth}{$2}{$3}}" returns "yes" or "no" (or "1" or "0").
Your logic is a bit incorrect though. Exim user has no permission to
open master.passwd. That's where your problem is.
As already mentioned, you can hack together a very simple server using
Net::Server that will communicate with Exim over a Unix socket. They you
could use ${readsocket{/var/spool/exim/auth.sock}{$2\n$3\n}}. The
daemon would wait for 2 lines - username in the first line, password in
the second. If the combo is ok it prints out "yes" and "no" otherwise.
A quick mockup could be (hacked together while writing this reply; it
seems to work, but hey, it can be fairly broken too - don't blame me):
=== START ===
#!/usr/bin/perl
package EximAuth;
use strict;
use vars qw(@ISA);
use Net::Server::PreFork;
@ISA = qw(Net::Server::PreFork);
my %users = (
foo => 'bar',
krot => 'org',
);
sub process_request {
my $self = shift;
my $sock = $self->{'server'}->{'client'};
chomp(my $username = $sock->getline());
chomp(my $password = $sock->getline());
# Here you should fetch preform the authentication, for now I just
# store usernames and cleartext passwords in a hash - this is
# supposed to be a quick hack to demonstrate how things can be done.
$sock->print($users{$username} eq $password ? 'yes' : 'no');
}
__PACKAGE__->run(
proto => 'unix',
port => '/var/spool/exim/auth.sock',
min_servers => 5,
min_spare_servers => 5,
max_spare_servers => 10,
max_servers => 15,
max_requests => 1000,
log_level => 0,
);
=== END ===
Now save this in a file, run it (as user who has permissions to create a
file in /var/spool/exim) and execute following:
exim -be '${readsocket{/var/spool/exim/auth.sock}{foo\nbar\n}}'
exim -be '${readsocket{/var/spool/exim/auth.sock}{foo\nbaz\n}}'
exim -be '${readsocket{/var/spool/exim/auth.sock}{krot\norg\n}}'
Make sure you understand how things work before you go further.
--
Kirill Miazine, Stud. Jur.
Faculty of Law, University of Oslo