secret@here 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. #!/usr/bin/perl
  2. #
  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.
  8. #
  9. $^W = 1;
  10. use strict;
  11. use IPC::Open2;
  12. $| = 1;
  13. # display extra information to STDERR
  14. my $debug = 0;
  15. if (scalar @ARGV) {
  16. $debug = 1;
  17. }
  18. # Globals
  19. my %state;
  20. my $default_key;
  21. my $next_key = $$;
  22. # loop over binary prompts
  23. for (;;) {
  24. my ($control, $data) = ReadBinaryPrompt();
  25. my ($reply_control, $reply_data);
  26. if ($control == 0) {
  27. if ($debug) {
  28. print STDERR "agent: no packet to read\n";
  29. }
  30. last;
  31. } elsif ($control == 0x02) {
  32. ($reply_control, $reply_data) = HandleAgentSelection($data);
  33. } elsif ($control == 0x01) {
  34. ($reply_control, $reply_data) = HandleContinuation($data);
  35. } else {
  36. if ($debug) {
  37. print STDERR
  38. "agent: unrecognized packet $control {$data} to read\n";
  39. }
  40. ($reply_control, $reply_data) = (0x04, "");
  41. }
  42. WriteBinaryPrompt($reply_control, $reply_data);
  43. }
  44. # Only willing to exit well if we've completed our authentication exchange
  45. if (scalar keys %state) {
  46. if ($debug) {
  47. print STDERR "The following sessions are still active:\n ";
  48. print STDERR join ', ', keys %state;
  49. print STDERR "\n";
  50. }
  51. exit 1;
  52. } else {
  53. exit 0;
  54. }
  55. sub HandleAgentSelection ($) {
  56. my ($data) = @_;
  57. unless ( $data =~ /^([a-zA-Z0-9_]+\@?[a-zA-Z0-9_.]*)\/(.*)$/ ) {
  58. return (0x04, "");
  59. }
  60. my ($agent_name, $payload) = ($1, $2);
  61. if ($debug) {
  62. print STDERR "agent: ". "agent=$agent_name, payload=$payload\n";
  63. }
  64. # this agent has a defined name
  65. if ($agent_name ne "secret\@here") {
  66. if ($debug) {
  67. print STDERR "bad agent name: [$agent_name]\n";
  68. }
  69. return (0x04, "");
  70. }
  71. # the selection request is acompanied with a hexadecimal cookie
  72. my @tokens = split '\|', $payload;
  73. unless ((scalar @tokens) == 2) {
  74. if ($debug) {
  75. print STDERR "bad payload\n";
  76. }
  77. return (0x04, "");
  78. }
  79. unless ($tokens[1] =~ /^[a-z0-9]+$/) {
  80. if ($debug) {
  81. print STDERR "bad server cookie\n";
  82. }
  83. return (0x04, "");
  84. }
  85. my $shared_secret = IdentifyLocalSecret($tokens[0]);
  86. unless (defined $shared_secret) {
  87. # make a secret up
  88. if ($debug) {
  89. print STDERR "agent: cannot authenticate user\n";
  90. }
  91. $shared_secret = GetRandom();
  92. }
  93. my $local_cookie = GetRandom();
  94. $default_key = $next_key++;
  95. $state{$default_key} = $local_cookie ."|". $tokens[1] ."|". $shared_secret;
  96. if ($debug) {
  97. print STDERR "agent: \$state{$default_key} = $state{$default_key}\n";
  98. }
  99. return (0x01, $default_key ."|". $local_cookie);
  100. }
  101. sub HandleContinuation ($) {
  102. my ($data) = @_;
  103. my ($key, $server_digest) = split '\|', $data;
  104. unless (defined $state{$key}) {
  105. # retries and out of sequence prompts are not permitted
  106. return (0x04, "");
  107. }
  108. my $expected_digest = CreateDigest($state{$key});
  109. my ($local_cookie, $remote_cookie, $shared_secret)
  110. = split '\|', $state{$key};
  111. delete $state{$key};
  112. unless ($expected_digest eq $server_digest) {
  113. if ($debug) {
  114. print STDERR "agent: don't trust server - faking reply\n";
  115. print STDERR "agent: got ($server_digest)\n";
  116. print STDERR "agent: expected ($expected_digest)\n";
  117. }
  118. ## FIXME: Agent should exchange a prompt with the client warning
  119. ## that the server is faking us out.
  120. return (0x03, CreateDigest($expected_digest . $data . GetRandom()));
  121. }
  122. if ($debug) {
  123. print STDERR "agent: server appears to know the secret\n";
  124. }
  125. my $session_authenticated_ticket =
  126. CreateDigest($remote_cookie."|".$shared_secret."|".$local_cookie);
  127. # FIXME: Agent should set a derived session key environment
  128. # variable (available for the client (and its children) to sign
  129. # future data exchanges.
  130. if ($debug) {
  131. print STDERR "agent: should putenv("
  132. ."\"AUTH_SESSION_TICKET=$session_authenticated_ticket\")\n";
  133. }
  134. # return agent's authenticating digest
  135. return (0x03, CreateDigest($shared_secret."|".$remote_cookie
  136. ."|".$local_cookie));
  137. }
  138. sub ReadBinaryPrompt {
  139. my $buffer = " ";
  140. my $count = read(STDIN, $buffer, 5);
  141. if ($count == 0) {
  142. # no more packets to read
  143. return (0, "");
  144. }
  145. if ($count != 5) {
  146. # broken packet header
  147. return (-1, "");
  148. }
  149. my ($length, $control) = unpack("N C", $buffer);
  150. if ($length < 5) {
  151. # broken packet length
  152. return (-1, "");
  153. }
  154. my $data = "";
  155. $length -= 5;
  156. while ($count = read(STDIN, $buffer, $length)) {
  157. $data .= $buffer;
  158. if ($count != $length) {
  159. $length -= $count;
  160. next;
  161. }
  162. if ($debug) {
  163. print STDERR "agent: ". "data is [$data]\n";
  164. }
  165. return ($control, $data);
  166. }
  167. # broken packet data
  168. return (-1, "");
  169. }
  170. sub WriteBinaryPrompt ($$) {
  171. my ($control, $data) = @_;
  172. my $length = 5 + length($data);
  173. if ($debug) {
  174. printf STDERR "agent: ". "{%d|0x%.2x|%s}\n", $length, $control, $data;
  175. }
  176. my $bp = pack("N C a*", $length, $control, $data);
  177. print STDOUT $bp;
  178. if ($debug) {
  179. printf STDERR "agent: ". "agent has replied\n";
  180. }
  181. }
  182. ##
  183. ## Here is where we parse the simple secret file
  184. ## The format of this file is a list of lines of the following form:
  185. ##
  186. ## user@client0.host.name secret_string1
  187. ## user@client1.host.name secret_string2
  188. ## user@client2.host.name secret_string3
  189. ##
  190. sub IdentifyLocalSecret ($) {
  191. my ($identifier) = @_;
  192. my $secret;
  193. if (open SECRETS, "< ". (getpwuid($<))[7] ."/.secret\@here") {
  194. my $line;
  195. while (defined ($line = <SECRETS>)) {
  196. my ($id, $sec) = split /[\s]+/, $line;
  197. if ((defined $id) && ($id eq $identifier)) {
  198. $secret = $sec;
  199. last;
  200. }
  201. }
  202. close SECRETS;
  203. }
  204. return $secret;
  205. }
  206. ## Here is where we generate a message digest
  207. sub CreateDigest ($) {
  208. my ($data) = @_;
  209. my $pid = open2(\*MD5out, \*MD5in, "/usr/bin/md5sum -")
  210. or die "you'll need /usr/bin/md5sum installed";
  211. my $oldfd = select MD5in; $|=1; select $oldfd;
  212. if ($debug) {
  213. print STDERR "agent: ". "telling md5: <$data>\n";
  214. }
  215. print MD5in "$data";
  216. close MD5in;
  217. my $reply = <MD5out>;
  218. ($reply) = split /\s/, $reply;
  219. if ($debug) {
  220. print STDERR "agent: ". "md5 said: <$reply>\n";
  221. }
  222. close MD5out;
  223. return $reply;
  224. }
  225. ## get a random number
  226. sub GetRandom {
  227. if ( -r "/dev/urandom" ) {
  228. open RANDOM, "< /dev/urandom" or die "crazy";
  229. my $i;
  230. my $reply = "";
  231. for ($i=0; $i<4; ++$i) {
  232. my $buffer = " ";
  233. while (read(RANDOM, $buffer, 4) != 4) {
  234. ;
  235. }
  236. $reply .= sprintf "%.8x", unpack("N", $buffer);
  237. if ($debug) {
  238. print STDERR "growing reply: [$reply]\n";
  239. }
  240. }
  241. close RANDOM;
  242. return $reply;
  243. } else {
  244. print STDERR "agent: ". "[got linux?]\n";
  245. return "%.8x%.8x%.8x%.8x", time, time, time, time;
  246. }
  247. }