#!/usr/bin/perl -Tw =head1 NAME auth_sharedsecret - Simple shared-secret auth plugin for qpsmtpd $Id: auth_sharedsecret 325 2004-07-18 05:13:18Z aqua $ =head1 DESCRIPTION auth_sharedsecret is a simple authentication scheme based on autogenerated passwords derived from the authenticating username and a particular secret key. It does not require a password file, and is compatible with PLAIN and CRAM-MD5 authentication methods. =head1 USAGE The password for any given user is equal to the lowercase hex representation of the MD5 digest of the concatenation of the username, a single space (0x20), and whatever secret you've specified in the configuration (see below). Hence to determine the password for a user 'bob' with a configured secret of 'secret', you could use: echo -n "bob secret"|md5sum The security of this scheme depends on (a) whether you disclose the secret to your users, and (b) how trustworthy your users are. Disclosure of the secret compromises all future authentications using that secret. However, it is not computationally feasible to determine the secret even knowing a user's password. =head1 CONFIGURATION Configuration of this plugin is in the form of name-value pairs, separated by whitespace. For example, your I file might contain a line such as: =over 2 I =back =head2 CONFIGURATION OPTIONS =over 2 =item B (e.g. I) The secret token used to generate per-user hashes. This option is mandatory. The key should be suitably entropic as to prevent it being recovered; a suitable chunk of printable non-whitespace characters from /dev/random will suffice. This is I the password you supply to your users. Rather, you concatenate each username together with this secret (see above for how) and give them the MD5 digest of the result. =item B (e.g. I) Enables or disables AUTH PLAIN authentication. Should be set to 1 or 0; the default is 1 (enabled). =item B (e.g. I) Enables or disables AUTH CRAM-MD5 authentication. Should be set to 1 or 0; the default is 1 (enabled). =back =head1 COPYRIGHT Devin Carraway This plugin is licensed under the same terms as the qpsmtpd package itself. Please see the LICENSE file included with qpsmtpd for details. You may also, at your option, use, modify and redistribute this plugin under the terms of the GNU General Public License version 2 or later. =cut use strict; use warnings; use Digest::MD5; sub register { my ( $self, $qp, @args ) = @_; my %args = @args; unless ($args{'secret'}) { $self->log(LOGERROR, "No 'secret' arg given in configuration"); return undef; } $self->{_secret} = $args{'secret'}; for ('plain', 'cram-md5') { $args{"allow-auth-$_"} = 1 unless defined $args{"allow-auth-$_"}; } $self->register_hook( "auth-plain", "auth_sharedsecret" ) if $args{'allow-auth-plain'}; $self->register_hook( "auth-cram-md5", "auth_sharedsecret" ) if $args{'allow-auth-cram-md5'}; 1; } sub auth_sharedsecret { my ( $self, $transaction, $method, $user, $clearpw, $hashpw, $ticket ) = @_; return (DENY, "No username given") unless $user; return (DENY, "No clearpw given") unless (($method eq 'plain' && $clearpw) || ($method eq 'cram-md5' && $hashpw)); my $ctx = new Digest::MD5; $ctx->add(lc $user . ' '); $ctx->add($self->{_secret}); my $expect_pw = $ctx->hexdigest; unless ($expect_pw) { $self->log(LOGERROR, "Error computing expected password fof $user"); return (DECLINED, "Internal error"); } my $auth_ok; if ($method eq 'plain') { if (lc $expect_pw eq lc $clearpw) { $self->log(LOGNOTICE, "Authenticated $user via auth-plain shared secret"); $auth_ok = 1; } else { $self->log(LOGWARN, "Authentication as $user failed (pw mismatch)"); $self->log(LOGINFO, "Expected ".lc($expect_pw).", got ".lc $clearpw); } } elsif ($method eq 'cram-md5') { eval 'use Digest::HMAC_MD5 qw(hmac_md5_hex);'; # The OO interface to HMAC_MD5 doesn't give the correct output my $digest = hmac_md5_hex($ticket, $expect_pw); if (lc $digest eq lc $hashpw) { $self->log(LOGNOTICE, "Authenticated $user via cram-md5 shared secret"); $auth_ok = 1; } else { $self->log(LOGWARN, "Authentication as $user failed (digest mismatch)"); $self->log(LOGINFO, "Expected ".lc($digest).", got ".lc $hashpw); } } else { $self->log(LOGERROR, "Unrecognized auth method $method"); } if ($auth_ok) { return (OK, "Authenticated $user; relaying permitted"); } else { return (DENY, "Authentication as $user failed"); } }