Configuring SASL Auth in Postfix


A) SASL Configuration  

 First ensure that Cyrus-SASL and salsauthd are installed.
 Then configure /etc/sysconfig/saslauthd so that SASL uses IMAP authentication =>

MECH=rimap
FLAGS="-O localhost" 
(Assuming that localhost is the IMAP Server)

restart saslauthd service

 test sasl by using the command - testsaslauthd. 

The testsaslauthd command for a user should return success. If it does, SASL configuration is fine. Now to configure Postfix.

B) Configure Postfix to use this underlying SASL =>

1.  Edit main.cf =>
# vim /etc/postfix/main.cf

smtpd_sasl_auth_enable = yes

broken_sasl_auth_clients = yes
smtpd_sasl_security_options = noanonymous


2. Edit /usr/lib/sasl2/smtpd.conf =>
pwcheck_method: saslauthd
mech_list: PLAIN LOGIN


To ensure that username and the "from" address in an email client match =>
Create the File
vim /etc/postfix/controlled_envelope_senders
    # envelope sender           owners (SASL login names)
    john@example.com            john@example.com
    abc@example.com             abc@example.com

 Now ensure that relaying is done thru SASL auth only, but for mynetworks =>
smtpd_sender_login_maps = hash:/etc/postfix/controlled_envelope_senders
smtpd_recipient_restrictions =
        reject_sender_login_mismatch
        permit_sasl_authenticated
        permit_mynetworks
        reject_unauth_destination

restart the Service
# /etc/init.d/postfix reload
# /etc/init.d/saslauthd  restart       

 Restart Postfix. Now if you do a telnet  on Port 25, you should receive the extra lines for auth =>
% telnet server.example.com 25
...
220 server.example.com ESMTP Postfix
EHLO client.example.com
250-server.example.com
250-PIPELINING
250-SIZE 10240000
250-AUTH PLAIN LOGIN
250-AUTH=PLAIN LOGIN

Generate the encoded password to be used while testing
            download the attached file - genauth.pl
             make sure you have installed - MIME::BASE64 package from CPAN
       execute it =>
Create file 
# vim genauth.pl
#####################################################################
#!/usr/bin/perl

use strict;
use MIME::Base64;
use Getopt::Std;

my($p_name)   = $0 =~ m|/?([^/]+)$|;
my $p_version = "20060620.0";
my $p_usage   = "Usage: $p_name [--help|--version] | <type> ...";
my $p_cp      = <<EOM;
        Copyright (c) 2002-2006 John Jetmore <jj33\@pobox.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
EOM
ext_usage();

my %O    = ();
getopts('s', \%O);

my $type = get_input(\@ARGV, "encryption type: ");

if ($type =~ /^plain$/i) {
  my $user = get_input(\@ARGV, "username: ", $O{s}||0);
  my $pass = get_input(\@ARGV, "password: ", $O{s}||1);
  print "Auth String: ", encode_base64("\0$user\0$pass", ''), "\n";

} elsif ($type =~ /^decode$/i) {
  my $user = get_input(\@ARGV, "string: ", $O{s}||0);
  print decode_base64($user), "\n";

} elsif ($type =~ /^encode$/i) {
  my $user = get_input(\@ARGV, "string: ", $O{s}||0);
  print encode_base64($user, ""), "\n";

} elsif ($type =~ /^rot13$/i) {
  my $str = get_input(\@ARGV, "string: ", $O{s}||0);
  my @c = unpack("c*", $str);
  foreach my $c (@c) {
    if    ($c <= 123 && $c >= 97) { $c = ((($c - 97 + 13) % 26) + 97); }
    elsif ($c <= 90  && $c >= 65) { $c = ((($c - 65 + 13) % 26) + 65); }
  }
  print pack("c*", @c), "\n";

} elsif ($type =~ /^atbash$/i) {
  my $str = get_input(\@ARGV, "string: ", $O{s}||0);
  my @c = unpack("c*", $str);
  foreach my $c (@c) {
    if    ($c <= 123 && $c >= 97) { $c = (25 - ($c - 97)) + 97; }
    elsif ($c <= 90  && $c >= 65) { $c = (25 - ($c - 65)) + 65; }
  }
  print pack("c*", @c), "\n";

} elsif ($type =~ /^http(-basic)?$/i) {
  my $user = get_input(\@ARGV, "username: ", $O{s}||0);
  my $pass = get_input(\@ARGV, "password: ", $O{s}||1);
  print "Auth String: ", encode_base64("${user}:$pass", ''), "\n";

} elsif ($type =~ /^wcsencode$/i) {
  try_load("WCS::Encode") || die "WCS::Encode required for rce\n";
  my $user = get_input(\@ARGV, "string: ", $O{s}||0);
  chomp($user = WCS::Encode::encode($user));
  print $user, "\n";

} elsif ($type =~ /^wcsdecode$/i) {
  try_load("WCS::Encode") || die "WCS::Encode required for rce\n";
  my $user = get_input(\@ARGV, "string: ", $O{s}||0);
  print WCS::Encode::decode($user), "\n";

} elsif ($type =~ /^rce$/i) {
  try_load("WCS::Passwd") || die "WCS::Passwd required for rce\n";
  my $user = get_input(\@ARGV, "string: ", $O{s}||0);
  print WCS::Passwd::rce($user), "\n";

} elsif ($type =~ /^rcd$/i) {
  try_load("WCS::Passwd") || die "WCS::Passwd required for rce\n";
  my $user = get_input(\@ARGV, "string: ", $O{s}||0);
  print WCS::Passwd::rcd($user), "\n";

} elsif ($type =~ /^(salt)?encrypt$/i) {
  my $user = get_input(\@ARGV, "string: ", $O{s}||1);
  my $salt = join('', ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64]);
  $salt    = get_input(\@ARGV, "salt: ", $O{s}||0) if ($type =~ /^saltencrypt/i);
  print crypt($user, $salt), "\n";

} elsif ($type =~ /^login$/i) {
  my $user = get_input(\@ARGV, "username: ", $O{s}||0);
  my $pass = get_input(\@ARGV, "password: ", $O{s}||1);
  print "Username: ", encode_base64($user, ""), "\n",
        "Password: ", encode_base64($pass, ""), "\n";

} elsif ($type =~ /^md5-(base)?64$/i) {
  try_load("Digest::MD5") || die "Digest::MD5 required for md5\n";
  my $string = get_input(\@ARGV, "string: ", $O{s}||0);
  print Digest::MD5::md5_base64($string), "\n";

} elsif ($type =~ /^md5(-hex)?$/i) {
  try_load("Digest::MD5") || die "Digest::MD5 required for md5\n";
  my $string = get_input(\@ARGV, "string: ", $O{s}||0);
  print Digest::MD5::md5_hex($string), "\n";

} elsif ($type =~ /^cram(-(md5|sha1))?$/i) {
  my $digest_type = lc($2) || 'md5';
  if ($digest_type eq 'md5') {
    try_load("Digest::MD5") || die "Digest::MD5 required for CRAM-MD5\n";
  } elsif ($digest_type eq 'sha1') {
    try_load("Digest::SHA1") || die "Digest::SHA1 required for CRAM-SHA1\n";
  }
  my $user = get_input(\@ARGV, "username: ", $O{s}||0);
  my $pass = get_input(\@ARGV, "password: ", $O{s}||1);
  my $chal = get_input(\@ARGV, "challenge: ", $O{s}||0);
  if ($chal !~ /^</) {
    chomp($chal = decode_base64($chal));
  }
  my $digest = get_digest($pass, $chal, $digest_type);
  print encode_base64("$user $digest", ""), "\n";

} elsif ($type =~ /^(ntlm|spa|msn)$/i) {
  try_load("Authen::NTLM") || die "Authen::NTLM required for $type\n";
  my $user = get_input(\@ARGV, "username: ", $O{s}||0);
  my $pass = get_input(\@ARGV, "password: ", $O{s}||1);
  my $domn = get_input(\@ARGV, "domain: ", $O{s}||0);
  print "Auth Request: ", Authen::NTLM::ntlm(), "\n";
  Authen::NTLM::ntlm_user($user);
  Authen::NTLM::ntlm_password($pass);
  Authen::NTLM::ntlm_domain($domn);
  my $chal = get_input(\@ARGV, "challenge: ", $O{s}||0);
  print "Auth Response: ", Authen::NTLM::ntlm($chal), "\n";

} elsif ($type =~ /^apop$/i) {
  try_load("Digest::MD5") || die "Digest::MD5 required for APOP\n";
  my $chal = get_input(\@ARGV, "challenge: ");
  my $pass = get_input(\@ARGV, "password: ", 1);
  my $ctx = Digest::MD5->new;
  $ctx->add($chal . $pass);
  print $ctx->hexdigest, "\n";

} else {
  print STDERR "I don't speak $type\n";
  exit 1;
}

exit 0;

sub get_input {
  my $a = shift; # command line array
  my $s = shift; # prompt string
  my $q = shift; # quiet
  my $r;         # response

  if (scalar(@$a) > 0) {
    $r = shift(@$a);
  } else {
    print $s;
    system('stty', '-echo') if ($q);
    $r = <>;
    system('stty', 'echo') if ($q);
    print "\n" if ($q);
    chomp($r);
  }

  $r = '' if ($r eq '<>');
  return($r);
}

sub get_digest {
  my $secr = shift;
  my $chal = shift;
  my $type = shift;
  my $ipad = chr(0x36) x 64;
  my $opad = chr(0x5c) x 64;

  if (length($secr) > 64) {
    if ($type eq 'md5') {
      $secr = Digest::MD5::md5($secr);
    } elsif ($type eq 'sha1') {
      $secr = Digest::SHA1::sha1($secr);
    } else {
      # unknown digest type
      return;
    }
  } else {
    $secr .= chr(0) x (64 - length($secr));
  }

  my $digest = $type eq 'md5'
               ? Digest::MD5::md5_hex(($secr ^ $opad),
                 Digest::MD5::md5(($secr ^ $ipad), $chal))
               : Digest::SHA1::sha1_hex(($secr ^ $opad),
                 Digest::SHA1::sha1(($secr ^ $ipad), $chal));
  return($digest);
}

sub try_load {
  my $mod = shift;

  eval("use $mod");
  return $@ ? 0 : 1;
}

sub ext_usage {
  if ($ARGV[0] =~ /^--help$/i) {
    require Config;
    $ENV{PATH} .= ":" unless $ENV{PATH} eq "";
    $ENV{PATH} = "$ENV{PATH}$Config::Config{'installscript'}";
    exec("perldoc", "-F", "-U", $0) || exit 1;
    # make parser happy
    %Config::Config = ();
  } elsif ($ARGV[0] =~ /^--version$/i) {
    print "$p_name version $p_version\n\n$p_cp\n";
  } else {
    return;
  }

  exit(0);
}

__END__

file Save & Quit 

####################################################################
[root@localhos~]#  chmod 555 genauth.pl 
[root@localhos~]# ./genauth.pl 
encryption type: plain
username: abc@example.com
password: 
Auth String: AHZiZ0B2Ymcua25hZmwub3JnAHZiZw==
Use "mail from" and "rcpt to" to send a test mail. It should ask for a password, 
use tha ebove string when you need to authorise

If all goes well, test with mail client

If you face an error, test the permissions for /var/run/saslauthd. Postfix should have read/write permissions on that.

Comments

Popular posts from this blog

How to install and configure node js and PM2 in rhel7

PCS Corosync Pacemaker Cluster Mariadb using NFS

How to Create or Configure iSCSI Server and Clinet