Skip to content

Commit 6c3d6b9

Browse files
committed
add GSSAPI (Kerberos) authentication
This version is compatible with buggy Authen::SASL::Perl::GSSAPI which always returns 1 from its need_step().
1 parent 573888d commit 6c3d6b9

2 files changed

Lines changed: 131 additions & 1 deletion

File tree

doc/base.pod

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,14 @@ The DIGEST-MD5 protocol's "digest-uri" values can be set using the C<--auth-extr
553553

554554
The CRAM-SHA1 authenticator requires the L<Digest::SHA> module. This type has only been tested against a non-standard implementation on an Exim server and may therefore have some implementation deficiencies.
555555

556+
=item GSSAPI
557+
558+
The GSSAPI authenticator requires the Authen::SASL module, as well as one of Authen::SASL::XS or Authen::SASL::Cyrus for the libsasl bindings.
559+
560+
Although the most commonly used GSSAPI mechanisms are passwordless, the authenticator will prompt for a password regardless. However, it will not acquire initial credentials on your behalf if the mechanism doesn't already do so. For instance, Kerberos requires you to obtain a TGT using "kinit" before authenticating.
561+
562+
The GSSAPI service and host can be set using the --auth-extra option; for instance, "--auth-extra gss-serv-type=lmtp,gss-host=mail.example.com".
563+
556564
=item NTLM/SPA/MSN
557565

558566
These authenticators require the Authen::NTLM module. Note that there are two modules using the L<Authen::NTLM> namespace on CPAN. The Mark Bush implementation (Authen/NTLM-1.03.tar.gz) is the version required by Swaks. This type has been tested against Exim, Communigate, and Exchange 2007.
@@ -601,6 +609,14 @@ The dmd5-host keyword is used by the DIGEST-MD5 authenticator and is used, in pa
601609

602610
The dmd5-serv-name keyword is used by the DIGEST-MD5 authenticator and is used, in part, to build the digest-uri-value string (see RFC2831)
603611

612+
=item gss-serv-type
613+
614+
The gss-serv-type keyword is used by the GSSAPI authenticator to specify the GSS service name (see RFC 4752).
615+
616+
=item gss-host
617+
618+
The gss-host keyword is used by the GSSAPI authenticator to specify the GSS hostname, which must match the server's fully qualified domain name (see RFC 4752).
619+
604620
=back
605621

606622
=item -am, --auth-map <key-value-pair>[,<key-value-pair>[,...]]

swaks

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,12 @@ sub do_smtp_auth {
714714
$auth_attempted = 1;
715715
}
716716
}
717+
foreach my $type (@{$G::auth_map_t{'GSSAPI'}}) {
718+
if ($btype eq $type) {
719+
return(0) if (do_smtp_auth_gssapi($au, $ap, $type));
720+
$auth_attempted = 1;
721+
}
722+
}
717723
foreach my $type (@{$G::auth_map_t{'NTLM'}}) {
718724
if ($btype eq $type) {
719725
return(0) if (do_smtp_auth_ntlm($au, $ap, $type));
@@ -737,6 +743,103 @@ sub do_smtp_auth {
737743
return $auth_attempted ? 4 : 2;
738744
}
739745

746+
sub do_smtp_auth_gssapi {
747+
my $u = shift; # auth user
748+
my $p = shift; # auth password
749+
my $as = shift; # auth string
750+
my $ro = ''; # will store smtp output
751+
my $ri = ''; # will store smtp input
752+
my $c = ''; # will store Authen::SASL status
753+
my $e = ''; # will store Authen::SASL errors
754+
my @gssapi_uri = ();
755+
756+
if (exists($G::auth_extras{"GSS-SERV-TYPE"})) {
757+
$gssapi_uri[0] = $G::auth_extras{"GSS-SERV-TYPE"};
758+
} else {
759+
$gssapi_uri[0] = 'smtp';
760+
}
761+
if (exists($G::auth_extras{"GSS-HOST"})) {
762+
$gssapi_uri[1] = $G::auth_extras{"GSS-HOST"};
763+
} else {
764+
if ($G::link{type} eq 'socket-inet') {
765+
$gssapi_uri[1] = $G::link{server};
766+
} else {
767+
# our local FQDN is the most sensible choice here, unlike in DIGEST-MD5
768+
$gssapi_uri[1] = get_fqdn(hostname());
769+
}
770+
}
771+
772+
my $callbacks = { user => $u, pass => $p };
773+
my $sasl = Authen::SASL->new(
774+
debug => 0,
775+
mechanism => 'GSSAPI',
776+
callback => $callbacks,
777+
);
778+
my $sasl_client = $sasl->client_new(@gssapi_uri);
779+
my $sasl_challenge;
780+
my $sasl_response;
781+
782+
# RFC 4752 (SASL GSSAPI) specifically defines Kerberos 5 and not any
783+
# other GSS-API mech. The mechanism always produces an initial client
784+
# response.
785+
786+
$sasl_response = $sasl_client->client_start();
787+
if (!length($sasl_response)) {
788+
$c = $sasl_client->code();
789+
$e = $sasl_client->error();
790+
ptrans('12', "Error received from Authen::SASL sub-system (client_start): [$c] $e");
791+
return(0);
792+
}
793+
794+
# RFC 4954 (SMTP SASL) allows the initial response to optionally be
795+
# part of AUTH if it fits in a SMTP line (i.e. maximum 738 byte raw
796+
# response). If IR not sent, the server must produce an empty "334 ".
797+
798+
$ro = "AUTH $as ".eb64($sasl_response);
799+
if (length($ro) > 998) {
800+
# Too long for IR; use the normal mechanism. Expect empty challenge.
801+
$ro = "AUTH $as";
802+
do_smtp_gen($ro, '334', \$ri, '', '', $G::auth_showpt ? \&unencode_smtp : '')
803+
|| return(0);
804+
$ri =~ s/^....//;
805+
if (length($ri) > 0) {
806+
ptrans('12', "Cancelling SASL exchange, unexpected data from server");
807+
return(0);
808+
}
809+
$ro = eb64($sasl_response);
810+
}
811+
# Otherwise carry the IR into the loop.
812+
813+
while (1) {
814+
do_smtp_gen($ro, qr/(334|235)/, \$ri, '',
815+
$G::auth_showpt ? "$sasl_response" : '',
816+
$G::auth_showpt ? \&unencode_smtp : '')
817+
|| return(0);
818+
819+
if (!$sasl_client->need_step()) {
820+
last;
821+
} elsif ($ri =~ /^235 /) {
822+
# Authentication succeeded, but Authen::SASL::Perl's need_step() is lying to us.
823+
# (Its Perl/GSSAPI.pm never calls set_success()...)
824+
# The loop could be simplified into 'while need_step' if that were fixed.
825+
if (ref($sasl_client) eq "Authen::SASL::Perl::GSSAPI") {
826+
ptrans('12', "SASL: assuming no more steps!");
827+
}
828+
last;
829+
} elsif ($ri =~ /^334 /) {
830+
$ri =~ s/^....//;
831+
$sasl_challenge = db64($ri);
832+
($sasl_response) = $sasl_client->client_step($sasl_challenge);
833+
$ro = eb64($sasl_response);
834+
} else {
835+
return(0);
836+
}
837+
}
838+
839+
return(0) if ($sasl_client->code() != 0);
840+
return(1);
841+
}
842+
740843
sub do_smtp_auth_ntlm {
741844
my $u = shift; # auth user
742845
my $p = shift; # auth password
@@ -1636,6 +1739,7 @@ sub load_dependencies {
16361739
req => [] },
16371740
auth_cram_md5 => { name => "AUTH CRAM-MD5", req => ['Digest::MD5'] },
16381741
auth_cram_sha1 => { name => "AUTH CRAM-SHA1", req => ['Digest::SHA'] },
1742+
auth_gssapi => { name => "AUTH GSSAPI", req => ['Authen::SASL'] },
16391743
auth_ntlm => { name => "AUTH NTLM", req => ['Authen::NTLM'] },
16401744
auth_digest_md5 => { name => "AUTH DIGEST-MD5", req => ['Authen::SASL'] },
16411745
dns => { name => "MX Routing", req => ['Net::DNS'] },
@@ -3362,7 +3466,7 @@ sub process_args {
33623466

33633467
# handle the --auth-map options plus our default mappings
33643468
foreach (split(/\s*,\s*/, get_arg('auth_map', $o)),"PLAIN=PLAIN","LOGIN=LOGIN",
3365-
"CRAM-MD5=CRAM-MD5","DIGEST-MD5=DIGEST-MD5",
3469+
"CRAM-MD5=CRAM-MD5","DIGEST-MD5=DIGEST-MD5","GSSAPI=GSSAPI",
33663470
"CRAM-SHA1=CRAM-SHA1","NTLM=NTLM","SPA=NTLM","MSN=NTLM")
33673471
{
33683472
if (/^([^=]+)=(.+)$/) {
@@ -3419,6 +3523,8 @@ sub process_args {
34193523
ptrans(12, avail_str("auth_cram_md5")) if ($auth_t ne 'ANY');
34203524
} elsif ($G::auth_map_f{$type} eq 'CRAM-SHA1' && !avail("auth_cram_sha1")) {
34213525
ptrans(12, avail_str("auth_cram_sha1")) if ($auth_t ne 'ANY');
3526+
} elsif ($G::auth_map_f{$type} eq 'GSSAPI' && !avail("auth_gssapi")) {
3527+
ptrans(12, avail_str("auth_gssapi")) if ($auth_t ne 'ANY');
34223528
} elsif ($G::auth_map_f{$type} eq 'NTLM' && !avail("auth_ntlm")) {
34233529
ptrans(12, avail_str("auth_ntlm")) if ($auth_t ne 'ANY');
34243530
} elsif ($G::auth_map_f{$type} eq 'DIGEST-MD5' && !avail("auth_digest_md5")) {
@@ -3772,6 +3878,14 @@ sub get_date_string {
37723878
return($G::date_string);
37733879
}
37743880

3881+
sub get_fqdn {
3882+
my $h = shift;
3883+
3884+
my @r = gethostbyname($h);
3885+
3886+
return $r[0] // $h;
3887+
}
3888+
37753889
# partially Cribbed from "Programming Perl" and MIME::Base64 v2.12
37763890
sub db64 {
37773891
my $s = shift;

0 commit comments

Comments
 (0)