Auth::SASL - Simple Authentication Security Layer
use Auth::SASL;
use Base64;
# naïve ESMTP handler...
my $smtp = IO::Socket::INET.new(:host<localhost>, :port(25));
$smtp.print("EHLO localhost\n");
my ($more, %properties);
repeat {
if $smtp.get ~~ /^ (\d\d\d)(<[-\ ]>)(.*) $/ {
my ($code, $more, $props) = ("$0", $1 eq "-", "$2");
if $props ~~ /(<[-\w]>+) >> <[=\ \t]>* (<-[\n]>*)/ {
%properties{ "$0" } = "$1";
}
}
else {
die "error occurred while initiating SMTP connection";
}
} while $more;
my $mechanisms = %properties<AUTH>;
my $sasl = Auth::SASL.new;
$sasl.begin-session(
data => %(
user => 'zostay',
pass => 'secret',
authname => 'zostay',
),
);
my ($response-code, $challenge);
MECHANISM: for $sasl.attempt-mechanisms($mechanisms, :service<smtp>, :host<localhost>) {
$smtp.print("AUTH $_.mechanism\n");
($response-code, $challenge) = $smtp.get.split(' ', 2);
next MECHANISM unless $response-code eq '334';
$challenge = '';
repeat {
$smtp.print(encode-base64(.step($challenge), :str) ~ "\n");
($response-code, $challenge) = $smtp.get.split(' ', 2);
next MECHANISM unless $response-code ~~ /^ 2 | 3 /;
} until .is-complete;
last MECHANISM if $response-code eq '235';
CATCH {
when X::Auth::SASL {
$smtp.print("*\n");
}
}
}
die "unable to authenticate" unless $response-code eq '235';
# Send email...
...
SASL stands for Simple Authentication and Security Layer. It is really just a means of exchanging authentication strings with a service. This library implements the client-side interaction for SASL. SASL itself is complicated only in the fact that it provides a means of exchanging authentication strings, but without specifying the protocol-specific details. Therefore, to use this module for a given protocol, any encoding, error handling, or other details must be implemented by the application developer.
This API is designed to work in a very modular way. This class, Auth::SASL
, is designed to operate as the front-end. As long as you are just making use of this module distribution to implement a SASL protocol for a client service, such as SMTP, IMAP, or LDAP, this class will provide most of what you need.
The factory objects are used to find and build mechanism objects. Mechanism objects provide handling to implement each kind of SASL authentication mechanism. The session objects provide the information about the identity that is being authenticated.
method new(Auth::SASL:U:
Auth::SASL::Session :$session,
Auth::SASL::Factory :$factory = Auth::SASL::Raku.new,
--> Auth::SASL:D
)
Constructs an Auth::SASL
front-end. Both arguments are optional. The $factory
defaults to Auth::SASL::Raku, which provides a pure Raku implementation of some SASL mechanisms. Without setting $session
, you will be required to call [.begin-session](#method begin-session) to initiate a session.
multi method begin-session(Auth::SASL:D: Auth::SASL::Session $session)
multi method begin-session(Auth::SASL:D: :%data, :&callback)
Starts a new session. The session provides information about the identity being authenticated.
For simple cases, you can provide %data
directly. These will be simple key/value pairs that provide the authentication details. The keys required will vary by the mechanisms involved.
For more complex cases you can provide a &callback
, this will be a Callable routine that will be called each time a mechanism object requires a property. The callback must provide a signature compatible with:
sub (Str:D $property-name, Str:D :$service, Str:D :$host --> Str)
It should always return defined string. If it does not and the authentication method depends on that value, authentication mechansim will fail with an X::Auth::SASL::Property exception.
Finally, you can provide an object implementing Auth::SASL::Session.
method end-session(Auth::SASL:D:)
Clears the session. The front-end will not work until a new session is started by calling [.begin-session](#method begin-session).
multi method supports-client-mechanisms(Auth::SASL:D: Str:D $mechanisms --> Bool:D)
multi method supports-client-mechanisms(Auth::SASL:D: Mixy:D $mechanisms --> Bool:D)
Returns a True
value if at least one of the mechanisms listed in $mechanisms
is supported by the current, False
otherwise.
multi method attempt-mechanisms(Auth::SASL:D: Str:D $mechanisms, Str:D :$service = '', Str:D :$host = '' --> Seq:D)
multi method attempt-mechanisms(Auth::SASL:D: Mixy:D $mechanisms, Str:D :$service = '', Str:D :$host = '' --> Seq:D)
Once a session has been established, this method may be called to itereate over potential authentication mechanisms. The $mechanisms
should be set to the list of mechanisms the server has reported as supporting. The factory will then use this and the list of mechanisms it supports to select the mechanisms to attempt. The Seq returned will allow you to iterate through 1 or more Auth::SASL::Mechanism::WorkingClient objects to use for authentication. If no compatible mechanism is found, the [X::Auth::SASL::NotFound](X::Auth::SASL#class X::Auth::SASL::NotFound) exception will be thrown.
If $mechanisms
is passed as a string, it may either be a single mechanism name, e.g., PLAIN
, or it it can be a space separated list of mechanism names, e.g., PLAIN ANONYMOUS
. The factory object will determine which mechanisms are most preferable in this case.
If $mechanisms
is passed as a Mixy object, such as a Mix or MixHash, the factory can use the score assigned to each key in the Mixy object to reorder the mechanisms and go in a different order.
The $service
and $host
parameters are optional and are used to allow a single session to connect to multiple services safely and even concurrently, so long as only a particular mechanism is permitted to make its attempt on a given $service
and $host
concurrently. The $service
and $host
parameters can also be used to allow a session to have multiple configurations for a given identity for different service/host combinations.