[Dovecot] Passwordless auth?
Wolfgang.Friebel at desy.de
Wolfgang.Friebel at desy.de
Fri May 24 10:20:32 EEST 2013
On Fri, 24 May 2013, Ben Morrow wrote:
> At 4PM -0700 on 23/05/13 you (Dan Mahoney, System Admin) wrote:
>>
>> I'm in the process of writing some scripts which I want to be able to take
>> actions on my local mailbox. (For example, to move a subset of messages
>> to the trash over time, if unread for a week. To act on messages in my
>> learn-spam folder and then delete them).
>
> http://wiki2.dovecot.org/PreAuth
>
> You can also use doveadm for quite a lot of this sort of administration;
> this may be easier if you're scripting in shell rather than something
> more sophisticated.
>
>> I'd definitely consider something like an SSH key with a forced
>> command (I do see questions in the FAQ about making dovecot work over a
>> socket connection), but that forgoes using standard imap clients.
>
> Well, I'm not sure what you consider 'standard' here, but there are both
> Perl and Python IMAP libraries which will connect to a command rather
> than a socket. If you're using a client which insists on connecting to
> an (INET) socket, it's a little harder; while you can obviously connect
> preauthed imap to a listening socket with netcat, that's not remotely
> secure.
>
>> I could also create a dovecot-only user with my UID and no other login
>> privileges, but I'd like this to "just work" for anyone.
>
> I believe with the latest 2.2 you can also do this with Kerberos
> principals, if you're running Kerberos; I haven't looked into this yet,
> but I mean to (for much the same reason).
>
> Ben
>
>
To access the mail storage on the imap server you can just speak the imap
protocol and authenticate against the imap server just like any other mail
client would do. If you are using Kerberos and have a ticket granting
ticket (after e.g. kinit) then the authentication against a properly
configured imap server is done without typing passwords. If the imap
server does support pam (and dovecot does) then this is handled there.
As an example I do attach a script that logs a user into an imap server
using Kerberos authentication and then displays the mail quota. Any other
action on the users mailboxes could be done as well. The script makes use
of SASL, therefore by changing the authenticate call and the callback
routine any other SASL mechanism could be used as well.
If you intend to perform tasks centrally for more than one user then
doveadm is certainly the right choice as was pointed out already
For accessing local mailboxes (i.e. not stored on an imap server) I'd
recommend one of the perl modules that can parse and process mailboxes
(typically in mbox format)
Wolfgang
-------------- next part --------------
#!/usr/bin/perl -w
use strict;
use vars qw ( %opt $imap $SERVICE $realm $host $gss_api_step $sasl $sasl_step $conn );
use Getopt::Std;
use Mail::IMAPClient;
use MIME::Base64;
use Term::ReadKey;
use Authen::Krb5;
# Authen::SASL::Cyrus needs to be installed as well !!!
# SASL2 needs to provide the gssapi auth library
use Authen::SASL;
use Authen::SASL::Cyrus;
(my $prog = $0) =~ s!.*/!!;
# on Solaris there is no system sasl2
if ( $^O eq 'solaris' ) {
$ENV{SASL_PATH} = "/usr/local/lib/sasl/2.1.15/lib/sasl2"
if -d "/usr/local/lib/sasl/2.1.15/lib/sasl2";
}
getopts('vh:r:u:', \%opt) or usage();
my $user = getusername();
Authen::Krb5::init_context() or die "no context: $@\n";
Authen::Krb5::init_ets();
my $realm = Authen::Krb5::get_default_realm();
if ( $opt{r} and $realm ne $opt{r} ) {
print "using realm $opt{r} instead of default realm $realm\n";
$realm = $opt{r};
}
die "Kerberos realm unknown, please provide it with -r\n" if ! $realm;
# get the host name(s) of the imap server(s)
# the IMAP server is often called mail or imap, let's assume it is called imap
my @hosts;
if ( $opt{h} ) {
@hosts = split(/[,\s]+/, $opt{h});
} else {
my $server = "imap.\L$realm";
my $rawip = gethostbyname($server);
$server = "mail.\L$realm" if ! $rawip;
$rawip = gethostbyname($server);
@hosts = ( $server ) if $rawip;
}
die "No imap server name found, please specify a valid name with -h\n"
if ! @hosts;
for $host ( @hosts ) {
$gss_api_step = $sasl_step = 0;
print "Connecting to $host:143 User $user\n" if $opt{v};
$imap = Mail::IMAPClient->new(
Server => $host,
User => $user,
) or die "couldn't connect to $host port 143: $!\n";
$SERVICE = 'imap';
$imap->authenticate('GSSAPI', \&gssapi_auth)
or die "Could not authenticate:$@\n";
# handle change in Mail::IMAPClient API since version 3
my ($quota, $maxquota);
my $major_version = substr($Mail::IMAPClient::VERSION, 0, 1);
if ( $major_version >= 3 ) {
$quota = ($imap->tag_and_run('GETQUOTAROOT "INBOX" '))[2];
} else {
$quota = ($imap->GETQUOTAROOT("INBOX"))[1];
}
if ( ! $@ ) {
($quota, $maxquota) = $quota =~ /STORAGE (\d+) (\d+)/;
if ( $maxquota ) {
printf "MAILQUOTA on %s: %d of %d kB used (%.1f percent)\n",
$host, $quota, $maxquota, 100*$quota/$maxquota;
}
} else {
print $imap->LastError, "\n";
}
$imap->logout; # or die "Logout error: ", $imap->LastError, "\n";
}
exit;
sub usage {
print <<EOF;
Usage: $prog [-h host] [-r realm] [-u user] [-v]
check the mail quota for a user. Authenticate using KerberosV.
-h host[s] check host[s] instead of default mail server
-r realm use realm instead of default realm
-u user use user as username, different from logged in user
-v show authentication details and folders processed
EOF
exit;
}
# required for the gssapi callback
sub getusername {
die "Invalid username '$opt{u}', must be alphanumeric\n"
if $opt{u} and $opt{u} !~ /^\w+$/;
return $opt{u} if $opt{u};
return getpwuid($<);
}
# gssapi_auth uses $SERVICE, $realm and $host from the calling environment
sub gssapi_auth {
$gss_api_step++;
if ( $gss_api_step == 1 ) {
print "Using Kerberos5 (gssapi) for authentication\n" if $opt{v};
$sasl = Authen::SASL->new(mechanism => 'GSSAPI',
callback => { user => \&getusername,
realm => $realm }
);
my $ac = Authen::Krb5::AuthContext->new() or die "no context: $@\n";
my $cc = Authen::Krb5::cc_default();
my $ticket = Authen::Krb5::mk_req($ac, 0, $SERVICE, $host, 0, $cc);
if ($user and ! $ticket) {
# system "kinit", $user;
my $psw = read_password($user);
my $client = Authen::Krb5::parse_name($user);
$realm = Authen::Krb5::get_default_realm();
my $server = Authen::Krb5::parse_name("krbtgt/$realm");
$cc->initialize($client);
my $i = Authen::Krb5::get_in_tkt_with_password($client, $server, $psw, $cc);
die "could not get ticket:$@\n", Authen::Krb5::error($i), "\n"
unless $i;
$ticket = Authen::Krb5::mk_req($ac, 0, $SERVICE, $host, 0, $cc);
$ticket or die "mk_req failed";
}
$conn = $sasl->client_new($SERVICE, $host);
my $err = $conn->error;
die "gssapi_auth error in client_new: $err\n" if $err !~ /successful/;
if ( ! grep {$_ eq 'GSSAPI' } $conn->global_listmech() ) {
die "SASL mechanism GSSAPI not available, known methods are\n",
join(', ', $conn->global_listmech()), "\n";
}
my $mesg = $conn->client_start;
$err = $conn->error;
die "gssapi_auth error in step $gss_api_step: $err\n" if $err !~ /successful/;
return encode_base64($mesg, '');
} else {
my $mesg=$conn->client_step(decode_base64($_[0]));
my $err = $conn->error;
#print "gssapi_auth error in step $gss_api_step: $err\n" if $err;
return encode_base64($mesg || '', '');
}
}
sub read_password {
local $|=1;
my $user = $_[0] || getusername;
print "Please enter (UNIX) password for user $user:";
ReadMode('noecho');
my $psw = ReadLine(0);
chomp $psw;
ReadMode('restore');
print "\n";
die "Empty password for $user, exiting.\n" unless $psw;
return $psw;
}
More information about the dovecot
mailing list