3 # This is a simple example PAM authentication agent, it implements a
4 # simple shared secret authentication scheme. The PAM module pam_secret.so
5 # is its counter part. Both the agent and the remote server are able to
6 # authenticate one another, but the server is given the opportunity to
7 # ignore a failed authentication.
15 # display extra information to STDERR
28 # loop over binary prompts
30 my ($control, $data) = ReadBinaryPrompt();
31 my ($reply_control, $reply_data);
35 print STDERR "agent: no packet to read\n";
38 } elsif ($control == 0x02) {
39 ($reply_control, $reply_data) = HandleAgentSelection($data);
40 } elsif ($control == 0x01) {
41 ($reply_control, $reply_data) = HandleContinuation($data);
45 "agent: unrecognized packet $control {$data} to read\n";
47 ($reply_control, $reply_data) = (0x04, "");
50 WriteBinaryPrompt($reply_control, $reply_data);
53 # Only willing to exit well if we've completed our authentication exchange
55 if (scalar keys %state) {
57 print STDERR "The following sessions are still active:\n ";
58 print STDERR join ', ', keys %state;
66 sub HandleAgentSelection ($) {
69 unless ( $data =~ /^([a-zA-Z0-9_]+\@?[a-zA-Z0-9_.]*)\/(.*)$/ ) {
73 my ($agent_name, $payload) = ($1, $2);
75 print STDERR "agent: ". "agent=$agent_name, payload=$payload\n";
78 # this agent has a defined name
79 if ($agent_name ne "secret\@here") {
81 print STDERR "bad agent name: [$agent_name]\n";
86 # the selection request is acompanied with a hexadecimal cookie
87 my @tokens = split '\|', $payload;
89 unless ((scalar @tokens) == 2) {
91 print STDERR "bad payload\n";
96 unless ($tokens[1] =~ /^[a-z0-9]+$/) {
98 print STDERR "bad server cookie\n";
103 my $shared_secret = IdentifyLocalSecret($tokens[0]);
105 unless (defined $shared_secret) {
108 print STDERR "agent: cannot authenticate user\n";
110 $shared_secret = GetRandom();
113 my $local_cookie = GetRandom();
114 $default_key = $next_key++;
116 $state{$default_key} = $local_cookie ."|". $tokens[1] ."|". $shared_secret;
119 print STDERR "agent: \$state{$default_key} = $state{$default_key}\n";
122 return (0x01, $default_key ."|". $local_cookie);
125 sub HandleContinuation ($) {
128 my ($key, $server_digest) = split '\|', $data;
130 unless (defined $state{$key}) {
131 # retries and out of sequence prompts are not permitted
135 my $expected_digest = CreateDigest($state{$key});
136 my ($local_cookie, $remote_cookie, $shared_secret)
137 = split '\|', $state{$key};
140 unless ($expected_digest eq $server_digest) {
142 print STDERR "agent: don't trust server - faking reply\n";
143 print STDERR "agent: got ($server_digest)\n";
144 print STDERR "agent: expected ($expected_digest)\n";
147 ## FIXME: Agent should exchange a prompt with the client warning
148 ## that the server is faking us out.
150 return (0x03, CreateDigest($expected_digest . $data . GetRandom()));
154 print STDERR "agent: server appears to know the secret\n";
157 my $session_authenticated_ticket =
158 CreateDigest($remote_cookie."|".$shared_secret."|".$local_cookie);
160 # FIXME: Agent should set a derived session key environment
161 # variable (available for the client (and its children) to sign
162 # future data exchanges.
165 print STDERR "agent: should putenv("
166 ."\"AUTH_SESSION_TICKET=$session_authenticated_ticket\")\n";
169 # return agent's authenticating digest
170 return (0x03, CreateDigest($shared_secret."|".$remote_cookie
171 ."|".$local_cookie));
174 sub ReadBinaryPrompt {
176 my $count = read(STDIN, $buffer, 5);
178 # no more packets to read
183 # broken packet header
187 my ($length, $control) = unpack("N C", $buffer);
189 # broken packet length
195 while ($count = read(STDIN, $buffer, $length)) {
197 if ($count != $length) {
203 print STDERR "agent: ". "data is [$data]\n";
206 return ($control, $data);
213 sub WriteBinaryPrompt ($$) {
214 my ($control, $data) = @_;
216 my $length = 5 + length($data);
218 printf STDERR "agent: ". "{%d|0x%.2x|%s}\n", $length, $control, $data;
220 my $bp = pack("N C a*", $length, $control, $data);
223 printf STDERR "agent: ". "agent has replied\n";
228 ## Here is where we parse the simple secret file
229 ## The format of this file is a list of lines of the following form:
231 ## user@client0.host.name secret_string1
232 ## user@client1.host.name secret_string2
233 ## user@client2.host.name secret_string3
236 sub IdentifyLocalSecret ($) {
237 my ($identifier) = @_;
240 if (open SECRETS, "< ". (getpwuid($<))[7] ."/.secret\@here") {
242 while (defined ($line = <SECRETS>)) {
243 my ($id, $sec) = split /[\s]+/, $line;
244 if ((defined $id) && ($id eq $identifier)) {
255 ## Here is where we generate a message digest
257 sub CreateDigest ($) {
260 my $pid = open2(\*MD5out, \*MD5in, "/usr/bin/md5sum -")
261 or die "you'll need /usr/bin/md5sum installed";
263 my $oldfd = select MD5in; $|=1; select $oldfd;
265 print STDERR "agent: ". "telling md5: <$data>\n";
269 my $reply = <MD5out>;
270 ($reply) = split /\s/, $reply;
272 print STDERR "agent: ". "md5 said: <$reply>\n";
279 ## get a random number
283 if ( -r "/dev/urandom" ) {
284 open RANDOM, "< /dev/urandom" or die "crazy";
289 for ($i=0; $i<4; ++$i) {
291 while (read(RANDOM, $buffer, 4) != 4) {
294 $reply .= sprintf "%.8x", unpack("N", $buffer);
296 print STDERR "growing reply: [$reply]\n";
303 print STDERR "agent: ". "[got linux?]\n";
304 return "%.8x%.8x%.8x%.8x", time, time, time, time;