123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- #!/usr/bin/perl
- #
- # This is a simple example PAM authentication agent, it implements a
- # simple shared secret authentication scheme. The PAM module pam_secret.so
- # is its counter part. Both the agent and the remote server are able to
- # authenticate one another, but the server is given the opportunity to
- # ignore a failed authentication.
- #
- $^W = 1;
- use strict;
- use IPC::Open2;
- $| = 1;
- # display extra information to STDERR
- my $debug = 0;
- if (scalar @ARGV) {
- $debug = 1;
- }
- # Globals
- my %state;
- my $default_key;
- my $next_key = $$;
- # loop over binary prompts
- for (;;) {
- my ($control, $data) = ReadBinaryPrompt();
- my ($reply_control, $reply_data);
- if ($control == 0) {
- if ($debug) {
- print STDERR "agent: no packet to read\n";
- }
- last;
- } elsif ($control == 0x02) {
- ($reply_control, $reply_data) = HandleAgentSelection($data);
- } elsif ($control == 0x01) {
- ($reply_control, $reply_data) = HandleContinuation($data);
- } else {
- if ($debug) {
- print STDERR
- "agent: unrecognized packet $control {$data} to read\n";
- }
- ($reply_control, $reply_data) = (0x04, "");
- }
- WriteBinaryPrompt($reply_control, $reply_data);
- }
- # Only willing to exit well if we've completed our authentication exchange
- if (scalar keys %state) {
- if ($debug) {
- print STDERR "The following sessions are still active:\n ";
- print STDERR join ', ', keys %state;
- print STDERR "\n";
- }
- exit 1;
- } else {
- exit 0;
- }
- sub HandleAgentSelection ($) {
- my ($data) = @_;
- unless ( $data =~ /^([a-zA-Z0-9_]+\@?[a-zA-Z0-9_.]*)\/(.*)$/ ) {
- return (0x04, "");
- }
- my ($agent_name, $payload) = ($1, $2);
- if ($debug) {
- print STDERR "agent: ". "agent=$agent_name, payload=$payload\n";
- }
- # this agent has a defined name
- if ($agent_name ne "secret\@here") {
- if ($debug) {
- print STDERR "bad agent name: [$agent_name]\n";
- }
- return (0x04, "");
- }
- # the selection request is acompanied with a hexadecimal cookie
- my @tokens = split '\|', $payload;
- unless ((scalar @tokens) == 2) {
- if ($debug) {
- print STDERR "bad payload\n";
- }
- return (0x04, "");
- }
- unless ($tokens[1] =~ /^[a-z0-9]+$/) {
- if ($debug) {
- print STDERR "bad server cookie\n";
- }
- return (0x04, "");
- }
- my $shared_secret = IdentifyLocalSecret($tokens[0]);
- unless (defined $shared_secret) {
- # make a secret up
- if ($debug) {
- print STDERR "agent: cannot authenticate user\n";
- }
- $shared_secret = GetRandom();
- }
- my $local_cookie = GetRandom();
- $default_key = $next_key++;
- $state{$default_key} = $local_cookie ."|". $tokens[1] ."|". $shared_secret;
- if ($debug) {
- print STDERR "agent: \$state{$default_key} = $state{$default_key}\n";
- }
- return (0x01, $default_key ."|". $local_cookie);
- }
- sub HandleContinuation ($) {
- my ($data) = @_;
- my ($key, $server_digest) = split '\|', $data;
- unless (defined $state{$key}) {
- # retries and out of sequence prompts are not permitted
- return (0x04, "");
- }
- my $expected_digest = CreateDigest($state{$key});
- my ($local_cookie, $remote_cookie, $shared_secret)
- = split '\|', $state{$key};
- delete $state{$key};
- unless ($expected_digest eq $server_digest) {
- if ($debug) {
- print STDERR "agent: don't trust server - faking reply\n";
- print STDERR "agent: got ($server_digest)\n";
- print STDERR "agent: expected ($expected_digest)\n";
- }
- ## FIXME: Agent should exchange a prompt with the client warning
- ## that the server is faking us out.
- return (0x03, CreateDigest($expected_digest . $data . GetRandom()));
- }
- if ($debug) {
- print STDERR "agent: server appears to know the secret\n";
- }
- my $session_authenticated_ticket =
- CreateDigest($remote_cookie."|".$shared_secret."|".$local_cookie);
- # FIXME: Agent should set a derived session key environment
- # variable (available for the client (and its children) to sign
- # future data exchanges.
- if ($debug) {
- print STDERR "agent: should putenv("
- ."\"AUTH_SESSION_TICKET=$session_authenticated_ticket\")\n";
- }
- # return agent's authenticating digest
- return (0x03, CreateDigest($shared_secret."|".$remote_cookie
- ."|".$local_cookie));
- }
- sub ReadBinaryPrompt {
- my $buffer = " ";
- my $count = read(STDIN, $buffer, 5);
- if ($count == 0) {
- # no more packets to read
- return (0, "");
- }
- if ($count != 5) {
- # broken packet header
- return (-1, "");
- }
- my ($length, $control) = unpack("N C", $buffer);
- if ($length < 5) {
- # broken packet length
- return (-1, "");
- }
- my $data = "";
- $length -= 5;
- while ($count = read(STDIN, $buffer, $length)) {
- $data .= $buffer;
- if ($count != $length) {
- $length -= $count;
- next;
- }
- if ($debug) {
- print STDERR "agent: ". "data is [$data]\n";
- }
- return ($control, $data);
- }
- # broken packet data
- return (-1, "");
- }
- sub WriteBinaryPrompt ($$) {
- my ($control, $data) = @_;
- my $length = 5 + length($data);
- if ($debug) {
- printf STDERR "agent: ". "{%d|0x%.2x|%s}\n", $length, $control, $data;
- }
- my $bp = pack("N C a*", $length, $control, $data);
- print STDOUT $bp;
- if ($debug) {
- printf STDERR "agent: ". "agent has replied\n";
- }
- }
- ##
- ## Here is where we parse the simple secret file
- ## The format of this file is a list of lines of the following form:
- ##
- ## user@client0.host.name secret_string1
- ## user@client1.host.name secret_string2
- ## user@client2.host.name secret_string3
- ##
- sub IdentifyLocalSecret ($) {
- my ($identifier) = @_;
- my $secret;
- if (open SECRETS, "< ". (getpwuid($<))[7] ."/.secret\@here") {
- my $line;
- while (defined ($line = <SECRETS>)) {
- my ($id, $sec) = split /[\s]+/, $line;
- if ((defined $id) && ($id eq $identifier)) {
- $secret = $sec;
- last;
- }
- }
- close SECRETS;
- }
- return $secret;
- }
- ## Here is where we generate a message digest
- sub CreateDigest ($) {
- my ($data) = @_;
- my $pid = open2(\*MD5out, \*MD5in, "/usr/bin/md5sum -")
- or die "you'll need /usr/bin/md5sum installed";
- my $oldfd = select MD5in; $|=1; select $oldfd;
- if ($debug) {
- print STDERR "agent: ". "telling md5: <$data>\n";
- }
- print MD5in "$data";
- close MD5in;
- my $reply = <MD5out>;
- ($reply) = split /\s/, $reply;
- if ($debug) {
- print STDERR "agent: ". "md5 said: <$reply>\n";
- }
- close MD5out;
- return $reply;
- }
- ## get a random number
- sub GetRandom {
- if ( -r "/dev/urandom" ) {
- open RANDOM, "< /dev/urandom" or die "crazy";
- my $i;
- my $reply = "";
- for ($i=0; $i<4; ++$i) {
- my $buffer = " ";
- while (read(RANDOM, $buffer, 4) != 4) {
- ;
- }
- $reply .= sprintf "%.8x", unpack("N", $buffer);
- if ($debug) {
- print STDERR "growing reply: [$reply]\n";
- }
- }
- close RANDOM;
- return $reply;
- } else {
- print STDERR "agent: ". "[got linux?]\n";
- return "%.8x%.8x%.8x%.8x", time, time, time, time;
- }
- }
|