summaryrefslogtreecommitdiff
path: root/libpamc/test
diff options
context:
space:
mode:
Diffstat (limited to 'libpamc/test')
-rwxr-xr-xlibpamc/test/agents/secret@here305
-rw-r--r--libpamc/test/modules/Makefile9
-rw-r--r--libpamc/test/modules/pam_secret.c670
-rw-r--r--libpamc/test/regress/Makefile7
-rwxr-xr-xlibpamc/test/regress/run_test.sh6
-rw-r--r--libpamc/test/regress/test.libpamc.c334
-rwxr-xr-xlibpamc/test/regress/test.secret@here152
7 files changed, 1483 insertions, 0 deletions
diff --git a/libpamc/test/agents/secret@here b/libpamc/test/agents/secret@here
new file mode 100755
index 00000000..18d8a661
--- /dev/null
+++ b/libpamc/test/agents/secret@here
@@ -0,0 +1,305 @@
+#!/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;
+ 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;
+ }
+
+}
+
diff --git a/libpamc/test/modules/Makefile b/libpamc/test/modules/Makefile
new file mode 100644
index 00000000..48065462
--- /dev/null
+++ b/libpamc/test/modules/Makefile
@@ -0,0 +1,9 @@
+CFLAGS = -g -fPIC -I"../../include"
+
+pam_secret.so: pam_secret.o
+ ld -x --shared -o pam_secret.so pam_secret.o -lc
+
+.o.c:
+
+clean:
+ rm -f *.so *.o
diff --git a/libpamc/test/modules/pam_secret.c b/libpamc/test/modules/pam_secret.c
new file mode 100644
index 00000000..04c7631b
--- /dev/null
+++ b/libpamc/test/modules/pam_secret.c
@@ -0,0 +1,670 @@
+/*
+ * $Id$
+ *
+ * Copyright (c) 1999 Andrew G. Morgan <morgan@linux.kernel.org>
+ */
+
+/*
+ * WARNING: AS WRITTEN THIS CODE IS NOT SECURE. THE MD5 IMPLEMENTATION
+ * NEEDS TO BE INTEGRATED MORE NATIVELY.
+ */
+
+/* #define DEBUG */
+
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <security/pam_modules.h>
+#include <security/pam_client.h>
+#include <security/_pam_macros.h>
+
+/*
+ * This is a sample module that demonstrates the use of binary prompts
+ * and how they can be used to implement sophisticated authentication
+ * schemes.
+ */
+
+struct ps_state_s {
+ int retval; /* last retval returned by the authentication fn */
+ int state; /* what state the module was in when it
+ returned incomplete */
+
+ char *username; /* the name of the local user */
+
+ char server_cookie[33]; /* storage for 32 bytes of server cookie */
+ char client_cookie[33]; /* storage for 32 bytes of client cookie */
+
+ char *secret_data; /* pointer to <NUL> terminated secret_data */
+ int invalid_secret; /* indication of whether the secret is valid */
+
+ pamc_bp_t current_prompt; /* place to store the current prompt */
+ pamc_bp_t current_reply; /* place to receive the reply prompt */
+};
+
+#define PS_STATE_ID "PAM_SECRET__STATE"
+#define PS_AGENT_ID "secret@here"
+#define PS_STATE_DEAD 0
+#define PS_STATE_INIT 1
+#define PS_STATE_PROMPT1 2
+#define PS_STATE_PROMPT2 3
+
+#define MAX_LEN_HOSTNAME 512
+#define MAX_FILE_LINE_LEN 1024
+
+/*
+ * Routine for generating 16*8 bits of random data represented in ASCII hex
+ */
+
+static int generate_cookie(unsigned char *buffer_33)
+{
+ static const char hexarray[] = "0123456789abcdef";
+ int i, fd;
+
+ /* fill buffer_33 with 32 hex characters (lower case) + '\0' */
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd < 0) {
+ D(("failed to open /dev/urandom"));
+ return 0;
+ }
+ read(fd, buffer_33 + 16, 16);
+ close(fd);
+
+ /* expand top 16 bytes into 32 nibbles */
+ for (i=0; i<16; ++i) {
+ buffer_33[2*i ] = hexarray[(buffer_33[16+i] & 0xf0)>>4];
+ buffer_33[2*i+1] = hexarray[(buffer_33[16+i] & 0x0f)];
+ }
+
+ buffer_33[32] = '\0';
+
+ return 1;
+}
+
+/*
+ * XXX - This is a hack, and is fundamentally insecure. Its subject to
+ * all sorts of attacks not to mention the fact that all our secrets
+ * will be displayed on the command line for someone doing 'ps' to
+ * see. This is just for programming convenience in this instance, it
+ * needs to be replaced with the md5 code. Although I am loath to
+ * add yet another instance of md5 code to the Linux-PAM source code.
+ * [Need to think of a cleaner way to do this for the distribution as
+ * a whole...]
+ */
+
+#define COMMAND_FORMAT "/bin/echo -n '%s|%s|%s'|/usr/bin/md5sum -"
+
+int create_digest(const char *d1, const char *d2, const char *d3,
+ char *buffer_33)
+{
+ int length;
+ char *buffer;
+ FILE *pipe;
+
+ length = strlen(d1)+strlen(d2)+strlen(d3)+sizeof(COMMAND_FORMAT);
+ buffer = malloc(length);
+ if (buffer == NULL) {
+ D(("out of memory"));
+ return 0;
+ }
+
+ sprintf(buffer, COMMAND_FORMAT, d1,d2,d3);
+
+ D(("executing pipe [%s]", buffer));
+ pipe = popen(buffer, "r");
+ memset(buffer, 0, length);
+ free(buffer);
+
+ if (pipe == NULL) {
+ D(("failed to launch pipe"));
+ return 0;
+ }
+
+ if (fgets(buffer_33, 33, pipe) == NULL) {
+ D(("failed to read digest"));
+ return 0;
+ }
+
+ if (strlen(buffer_33) != 32) {
+ D(("digest was not 32 chars"));
+ return 0;
+ }
+
+ fclose(pipe);
+
+ D(("done [%s]", buffer_33));
+
+ return 1;
+}
+
+/*
+ * method to attempt to instruct the application's conversation function
+ */
+
+static int converse(pam_handle_t *pamh, struct ps_state_s *new)
+{
+ int retval;
+ struct pam_conv *conv;
+
+ D(("called"));
+
+ retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
+ if (retval == PAM_SUCCESS) {
+ struct pam_message msg;
+ struct pam_response *single_reply;
+ const struct pam_message *msg_ptr;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_style = PAM_BINARY_PROMPT;
+ msg.msg = (const char *) new->current_prompt;
+ msg_ptr = &msg;
+
+ single_reply = NULL;
+ retval = conv->conv(1, &msg_ptr, &single_reply, conv->appdata_ptr);
+ if (retval == PAM_SUCCESS) {
+ if ((single_reply == NULL) || (single_reply->resp == NULL)) {
+ retval == PAM_CONV_ERR;
+ } else {
+ new->current_reply = (pamc_bp_t) single_reply->resp;
+ single_reply->resp = NULL;
+ }
+ }
+
+ if (single_reply) {
+ free(single_reply);
+ }
+ }
+
+#ifdef DEBUG
+ if (retval == PAM_SUCCESS) {
+ D(("reply has length=%d and control=%u",
+ PAM_BP_LENGTH(new->current_reply),
+ PAM_BP_CONTROL(new->current_reply)));
+ }
+ D(("returning %s", pam_strerror(pamh, retval)));
+#endif
+
+ return retval;
+}
+
+/*
+ * identify the secret in question
+ */
+
+#define SECRET_FILE_FORMAT "%s/.secret@here"
+
+char *identify_secret(char *identity, const char *user)
+{
+ struct passwd *pwd;
+ char *temp;
+ FILE *secrets;
+ int length_id;
+
+ pwd = getpwnam(user);
+ if ((pwd == NULL) || (pwd->pw_dir == NULL)) {
+ D(("user [%s] is not known", user));
+ }
+
+ length_id = strlen(pwd->pw_dir) + sizeof(SECRET_FILE_FORMAT);
+ temp = malloc(length_id);
+ if (temp == NULL) {
+ D(("out of memory"));
+ pwd = NULL;
+ return NULL;
+ }
+
+ sprintf(temp, SECRET_FILE_FORMAT, pwd->pw_dir);
+ pwd = NULL;
+
+ D(("opening key file [%s]", temp));
+ secrets = fopen(temp, "r");
+ memset(temp, 0, length_id);
+
+ if (secrets == NULL) {
+ D(("failed to open key file"));
+ return NULL;
+ }
+
+ length_id = strlen(identity);
+ temp = malloc(MAX_FILE_LINE_LEN);
+
+ for (;;) {
+ char *secret = NULL;
+
+ if (fgets(temp, MAX_FILE_LINE_LEN, secrets) == NULL) {
+ fclose(secrets);
+ return NULL;
+ }
+
+ D(("cf[%s][%s]", identity, temp));
+ if (memcmp(temp, identity, length_id)) {
+ continue;
+ }
+
+ D(("found entry"));
+ fclose(secrets);
+
+ for (secret=temp+length_id; *secret; ++secret) {
+ if (!(*secret == ' ' || *secret == '\n' || *secret == '\t')) {
+ break;
+ }
+ }
+
+ memmove(temp, secret, MAX_FILE_LINE_LEN-(secret-(temp+length_id)));
+ secret = temp;
+
+ for (; *secret; ++secret) {
+ if (*secret == ' ' || *secret == '\n' || *secret == '\t') {
+ break;
+ }
+ }
+
+ if (*secret) {
+ *secret = '\0';
+ }
+
+ D(("secret found [%s]", temp));
+
+ return temp;
+ }
+
+ /* NOT REACHED */
+}
+
+/*
+ * function to perform the two message authentication process
+ * (with support for event driven conversation functions)
+ */
+
+static int auth_sequence(pam_handle_t *pamh,
+ const struct ps_state_s *old, struct ps_state_s *new)
+{
+ const char *rhostname;
+ const char *rusername;
+ int retval;
+
+ retval = pam_get_item(pamh, PAM_RUSER, (const void **) &rusername);
+ if ((retval != PAM_SUCCESS) || (rusername == NULL)) {
+ D(("failed to obtain an rusername"));
+ new->state = PS_STATE_DEAD;
+ return PAM_AUTH_ERR;
+ }
+
+ retval = pam_get_item(pamh, PAM_RHOST, (const void **) &rhostname);
+ if ((retval != PAM_SUCCESS) || (rhostname == NULL)) {
+ D(("failed to identify local hostname: ", pam_strerror(pamh, retval)));
+ new->state = PS_STATE_DEAD;
+ return PAM_AUTH_ERR;
+ }
+
+ D(("switch on new->state=%d [%s@%s]", new->state, rusername, rhostname));
+ switch (new->state) {
+
+ case PS_STATE_INIT:
+ {
+ const char *user = NULL;
+
+ retval = pam_get_user(pamh, &user, NULL);
+
+ if ((retval == PAM_SUCCESS) && (user == NULL)) {
+ D(("success but no username?"));
+ new->state = PS_STATE_DEAD;
+ retval = PAM_USER_UNKNOWN;
+ }
+
+ if (retval != PAM_SUCCESS) {
+ if (retval == PAM_CONV_AGAIN) {
+ retval = PAM_INCOMPLETE;
+ } else {
+ new->state = PS_STATE_DEAD;
+ }
+ D(("state init failed: %s", pam_strerror(pamh, retval)));
+ return retval;
+ }
+
+ /* nothing else in this 'case' can be retried */
+
+ new->username = strdup(user);
+ if (new->username == NULL) {
+ D(("out of memory"));
+ new->state = PS_STATE_DEAD;
+ return PAM_BUF_ERR;
+ }
+
+ if (! generate_cookie(new->server_cookie)) {
+ D(("problem generating server cookie"));
+ new->state = PS_STATE_DEAD;
+ return PAM_ABORT;
+ }
+
+ new->current_prompt = NULL;
+ PAM_BP_RENEW(&new->current_prompt, PAM_BPC_SELECT,
+ sizeof(PS_AGENT_ID) + strlen(rusername) + 1
+ + strlen(rhostname) + 1 + 32);
+ sprintf(PAM_BP_DATA(new->current_prompt),
+ PS_AGENT_ID "/%s@%s|%.32s", rusername, rhostname,
+ new->server_cookie);
+
+ /* note, the BP is guaranteed by the spec to be <NUL> terminated */
+ D(("initialization packet [%s]", PAM_BP_DATA(new->current_prompt)));
+
+ /* fall through */
+ new->state = PS_STATE_PROMPT1;
+
+ D(("fall through to state_prompt1"));
+ }
+
+ case PS_STATE_PROMPT1:
+ {
+ int i, length;
+
+ /* send {secret@here/jdoe@client.host|<s_cookie>} */
+ retval = converse(pamh, new);
+ if (retval != PAM_SUCCESS) {
+ if (retval == PAM_CONV_AGAIN) {
+ D(("conversation failed to complete"));
+ return PAM_INCOMPLETE;
+ } else {
+ new->state = PS_STATE_DEAD;
+ return retval;
+ }
+ }
+
+ if (retval != PAM_SUCCESS) {
+ D(("failed to read ruser@rhost"));
+ new->state = PS_STATE_DEAD;
+ return PAM_AUTH_ERR;
+ }
+
+ /* expect to receive the following {<seqid>|<a_cookie>} */
+ if (new->current_reply == NULL) {
+ D(("converstation returned [%s] but gave no reply",
+ pam_strerror(pamh, retval)));
+ new->state = PS_STATE_DEAD;
+ return PAM_CONV_ERR;
+ }
+
+ /* find | */
+ length = PAM_BP_LENGTH(new->current_reply);
+ for (i=0; i<length; ++i) {
+ if (PAM_BP_DATA(new->current_reply)[i] == '|') {
+ break;
+ }
+ }
+ if (i >= length) {
+ D(("malformed response (no |) of length %d", length));
+ new->state = PS_STATE_DEAD;
+ return PAM_CONV_ERR;
+ }
+ if ((length - ++i) != 32) {
+ D(("cookie is incorrect length (%d,%d) %d != 32",
+ length, i, length-i));
+ new->state = PS_STATE_DEAD;
+ return PAM_CONV_ERR;
+ }
+
+ /* copy client cookie */
+ memcpy(new->client_cookie, PAM_BP_DATA(new->current_reply)+i, 32);
+
+ /* generate a prompt that is length(seqid) + length(|) + 32 long */
+ PAM_BP_RENEW(&new->current_prompt, PAM_BPC_OK, i+32);
+ /* copy the head of the response prompt */
+ memcpy(PAM_BP_DATA(new->current_prompt),
+ PAM_BP_DATA(new->current_reply), i);
+ PAM_BP_RENEW(&new->current_reply, 0, 0);
+
+ /* look up the secret */
+ new->invalid_secret = 0;
+
+ if (new->secret_data == NULL) {
+ char *ruser_rhost;
+
+ ruser_rhost = malloc(strlen(rusername)+2+strlen(rhostname));
+ if (ruser_rhost == NULL) {
+ D(("out of memory"));
+ new->state = PS_STATE_DEAD;
+ return PAM_BUF_ERR;
+ }
+ sprintf(ruser_rhost, "%s@%s", rusername, rhostname);
+ new->secret_data = identify_secret(ruser_rhost, new->username);
+
+ memset(ruser_rhost, 0, strlen(ruser_rhost));
+ free(ruser_rhost);
+ }
+
+ if (new->secret_data == NULL) {
+ D(("secret not found for user"));
+ new->invalid_secret = 1;
+
+ /* need to make up a secret */
+ new->secret_data = malloc(32 + 1);
+ if (new->secret_data == NULL) {
+ D(("out of memory"));
+ new->state = PS_STATE_DEAD;
+ return PAM_BUF_ERR;
+ }
+ if (! generate_cookie(new->secret_data)) {
+ D(("what's up - no fake cookie generated?"));
+ new->state = PS_STATE_DEAD;
+ return PAM_ABORT;
+ }
+ }
+
+ /* construct md5[<client_cookie>|<server_cookie>|<secret_data>] */
+ if (! create_digest(new->client_cookie, new->server_cookie,
+ new->secret_data,
+ PAM_BP_DATA(new->current_prompt)+i)) {
+ D(("md5 digesting failed"));
+ new->state = PS_STATE_DEAD;
+ return PAM_ABORT;
+ }
+
+ /* prompt2 is now constructed - fall through to send it */
+ }
+
+ case PS_STATE_PROMPT2:
+ {
+ /* send {<seqid>|md5[<client_cookie>|<server_cookie>|<secret_data>]} */
+ retval = converse(pamh, new);
+ if (retval != PAM_SUCCESS) {
+ if (retval == PAM_CONV_AGAIN) {
+ D(("conversation failed to complete"));
+ return PAM_INCOMPLETE;
+ } else {
+ new->state = PS_STATE_DEAD;
+ return retval;
+ }
+ }
+
+ /* After we complete this section, we should not be able to
+ recall this authentication function. So, we force all
+ future calls into the weeds. */
+
+ new->state = PS_STATE_DEAD;
+
+ /* expect reply:{md5[<secret_data>|<server_cookie>|<client_cookie>]} */
+
+ {
+ int cf;
+ char expectation[33];
+
+ if (!create_digest(new->secret_data, new->server_cookie,
+ new->client_cookie, expectation)) {
+ new->state = PS_STATE_DEAD;
+ return PAM_ABORT;
+ }
+
+ cf = strcmp(expectation, PAM_BP_DATA(new->current_reply));
+ memset(expectation, 0, sizeof(expectation));
+ if (cf || new->invalid_secret) {
+ D(("failed to authenticate"));
+ return PAM_AUTH_ERR;
+ }
+ }
+
+ D(("correctly authenticated :)"));
+ return PAM_SUCCESS;
+ }
+
+ default:
+ new->state = PS_STATE_DEAD;
+
+ case PS_STATE_DEAD:
+
+ D(("state is currently dead/unknown"));
+ return PAM_AUTH_ERR;
+ }
+
+ fprintf(stderr, "pam_secret: this should not be reached\n");
+ return PAM_ABORT;
+}
+
+static void clean_data(pam_handle_t *pamh, void *datum, int error_status)
+{
+ struct ps_state_s *data = datum;
+
+ D(("liberating datum=%p", datum));
+
+ if (data) {
+ D(("renew prompt"));
+ PAM_BP_RENEW(&data->current_prompt, 0, 0);
+ D(("renew reply"));
+ PAM_BP_RENEW(&data->current_reply, 0, 0);
+ D(("overwrite datum"));
+ memset(data, 0, sizeof(struct ps_state_s));
+ D(("liberate datum"));
+ free(data);
+ }
+
+ D(("done."));
+}
+
+/*
+ * front end for the authentication function
+ */
+
+int pam_sm_authenticate(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
+{
+ int retval;
+ struct ps_state_s *new_data;
+ const struct ps_state_s *old_data;
+
+ D(("called"));
+
+ new_data = calloc(1, sizeof(struct ps_state_s));
+ if (new_data == NULL) {
+ D(("out of memory"));
+ return PAM_BUF_ERR;
+ }
+ new_data->retval = PAM_SUCCESS;
+
+ retval = pam_get_data(pamh, PS_STATE_ID, (const void **) &old_data);
+ if (retval == PAM_SUCCESS) {
+ new_data->state = old_data->state;
+ memcpy(new_data->server_cookie, old_data->server_cookie, 32);
+ memcpy(new_data->client_cookie, old_data->client_cookie, 32);
+ if (old_data->username) {
+ new_data->username = strdup(old_data->username);
+ }
+ if (old_data->secret_data) {
+ new_data->secret_data = strdup(old_data->secret_data);
+ }
+ if (old_data->current_prompt) {
+ int length;
+
+ length = PAM_BP_LENGTH(old_data->current_prompt);
+ PAM_BP_RENEW(&new_data->current_prompt,
+ PAM_BP_CONTROL(old_data->current_prompt), length);
+ PAM_BP_FILL(new_data->current_prompt, 0, length,
+ PAM_BP_DATA(old_data->current_prompt));
+ }
+ /* don't need to duplicate current_reply */
+ } else {
+ old_data = NULL;
+ new_data->state = PS_STATE_INIT;
+ }
+
+ D(("call auth_sequence"));
+ new_data->retval = auth_sequence(pamh, old_data, new_data);
+ D(("returned from auth_sequence"));
+
+ retval = pam_set_data(pamh, PS_STATE_ID, new_data, clean_data);
+ if (retval != PAM_SUCCESS) {
+ D(("unable to store new_data"));
+ } else {
+ retval = new_data->retval;
+ }
+
+ old_data = new_data = NULL;
+
+ D(("done (%d)", retval));
+ return retval;
+}
+
+/*
+ * front end for the credential setting function
+ */
+
+#define AUTH_SESSION_TICKET_ENV_FORMAT "AUTH_SESSION_TICKET="
+
+int pam_sm_setcred(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
+{
+ int retval;
+ const struct ps_state_s *old_data;
+
+ D(("called"));
+
+ /* XXX - need to pay attention to the various flavors of call */
+
+ /* XXX - need provide an option to turn this feature on/off: if
+ other modules want to supply an AUTH_SESSION_TICKET, we should
+ leave it up to the admin which module dominiates. */
+
+ retval = pam_get_data(pamh, PS_STATE_ID, (const void **) &old_data);
+ if (retval != PAM_SUCCESS) {
+ D(("no data to base decision on"));
+ return PAM_AUTH_ERR;
+ }
+
+ /*
+ * If ok, export a derived shared secret session ticket to the
+ * client's PAM environment - the ticket has the form
+ *
+ * AUTH_SESSION_TICKET =
+ * md5[<server_cookie>|<secret_data>|<client_cookie>]
+ *
+ * This is a precursor to supporting a spoof resistant trusted
+ * path mechanism. This shared secret ticket can be used to add
+ * a hard-to-guess checksum to further authentication data.
+ */
+
+ retval = old_data->retval;
+ if (retval == PAM_SUCCESS) {
+ char envticket[sizeof(AUTH_SESSION_TICKET_ENV_FORMAT)+32];
+
+ memcpy(envticket, AUTH_SESSION_TICKET_ENV_FORMAT,
+ sizeof(AUTH_SESSION_TICKET_ENV_FORMAT));
+
+ if (! create_digest(old_data->server_cookie, old_data->secret_data,
+ old_data->client_cookie,
+ envticket+sizeof(AUTH_SESSION_TICKET_ENV_FORMAT)-1
+ )) {
+ D(("unable to generate a digest for session ticket"));
+ return PAM_ABORT;
+ }
+
+ D(("putenv[%s]", envticket));
+ retval = pam_putenv(pamh, envticket);
+ memset(envticket, 0, sizeof(envticket));
+ }
+
+ old_data = NULL;
+ D(("done (%d)", retval));
+
+ return retval;
+}
diff --git a/libpamc/test/regress/Makefile b/libpamc/test/regress/Makefile
new file mode 100644
index 00000000..ff63e5f0
--- /dev/null
+++ b/libpamc/test/regress/Makefile
@@ -0,0 +1,7 @@
+CFLAGS = -g -I ../../include
+
+test.libpamc: test.libpamc.o
+ $(CC) -o $@ $< -L ../.. -lpamc
+
+clean:
+ rm -f test.libpamc test.libpamc.o
diff --git a/libpamc/test/regress/run_test.sh b/libpamc/test/regress/run_test.sh
new file mode 100755
index 00000000..a1bf010b
--- /dev/null
+++ b/libpamc/test/regress/run_test.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+export LD_LIBRARY_PATH=../..
+export PAMC_AGENT_PATH="../agents"
+
+./test.libpamc
diff --git a/libpamc/test/regress/test.libpamc.c b/libpamc/test/regress/test.libpamc.c
new file mode 100644
index 00000000..b5fb1b82
--- /dev/null
+++ b/libpamc/test/regress/test.libpamc.c
@@ -0,0 +1,334 @@
+/*
+ * This is a small test program for testing libpamc against the
+ * secret@here agent. It does the same as the test.secret@here perl
+ * script in this directory, but via the libpamc API.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <security/pam_client.h>
+#include <ctype.h>
+
+struct internal_packet {
+ int length;
+ int at;
+ char *buffer;
+};
+
+
+void append_data(struct internal_packet *packet, int extra, const char *data)
+{
+ if ((extra + packet->at) >= packet->length) {
+ if (packet->length == 0) {
+ packet->length = 1000;
+ }
+ /* make sure we have at least a char extra space available */
+ while (packet->length <= (extra + packet->at)) {
+ packet->length <<= 1;
+ }
+ packet->buffer = realloc(packet->buffer, packet->length);
+ if (packet->buffer == NULL) {
+ fprintf(stderr, "out of memory\n");
+ exit(1);
+ }
+ }
+
+ if (data != NULL) {
+ memcpy(packet->at + packet->buffer, data, extra);
+ }
+ packet->at += extra;
+
+ /* assisting string manipulation */
+ packet->buffer[packet->at] = '\0';
+}
+
+void append_string(struct internal_packet *packet, const char *string,
+ int with_nul)
+{
+ append_data(packet, strlen(string) + (with_nul ? 1:0), string);
+}
+
+char *identify_secret(char *identity)
+{
+ struct internal_packet temp_packet;
+ FILE *secrets;
+ int length_id;
+
+ temp_packet.length = temp_packet.at = 0;
+ temp_packet.buffer = NULL;
+
+ append_string(&temp_packet, "/home/", 0);
+ append_string(&temp_packet, getlogin(), 0);
+ append_string(&temp_packet, "/.secret@here", 1);
+
+ secrets = fopen(temp_packet.buffer, "r");
+ if (secrets == NULL) {
+ fprintf(stderr, "server: failed to open\n [%s]\n",
+ temp_packet.buffer);
+ exit(1);
+ }
+
+ length_id = strlen(identity);
+ for (;;) {
+ char *secret = NULL;
+ temp_packet.at = 0;
+
+ if (fgets(temp_packet.buffer, temp_packet.length, secrets) == NULL) {
+ fclose(secrets);
+ return NULL;
+ }
+
+ if (memcmp(temp_packet.buffer, identity, length_id)) {
+ continue;
+ }
+
+ fclose(secrets);
+ for (secret=temp_packet.buffer; *secret; ++secret) {
+ if (*secret == ' ' || *secret == '\n' || *secret == '\t') {
+ break;
+ }
+ }
+ for (; *secret; ++secret) {
+ if (!(*secret == ' ' || *secret == '\n' || *secret == '\t')) {
+ break;
+ }
+ }
+
+ for (temp_packet.buffer=secret; *temp_packet.buffer;
+ ++temp_packet.buffer) {
+ if (*temp_packet.buffer == ' ' || *temp_packet.buffer == '\n'
+ || *temp_packet.buffer == '\t') {
+ break;
+ }
+ }
+ if (*temp_packet.buffer) {
+ *temp_packet.buffer = '\0';
+ }
+
+ return secret;
+ }
+
+ /* NOT REACHED */
+}
+
+/*
+ * This is a hack, and is fundamentally insecure. All our secrets will be
+ * displayed on the command line for someone doing 'ps' to see. This
+ * is just for programming convenience in this instance, since this
+ * program is simply a regression test. The pam_secret module should
+ * not do this, but make use of md5 routines directly.
+ */
+
+char *create_digest(int length, const char *raw)
+{
+ struct internal_packet temp_packet;
+ FILE *pipe;
+
+ temp_packet.length = temp_packet.at = 0;
+ temp_packet.buffer = NULL;
+
+ append_string(&temp_packet, "/bin/echo -n '", 0);
+ append_string(&temp_packet, raw, 0);
+ append_string(&temp_packet, "'|/usr/bin/md5sum -", 1);
+
+ pipe = popen(temp_packet.buffer, "r");
+ if (pipe == NULL) {
+ fprintf(stderr, "server: failed to run\n [%s]\n", temp_packet.buffer);
+ exit(1);
+ }
+
+ temp_packet.at = 0;
+ append_data(&temp_packet, 32, NULL);
+
+ if (fgets(temp_packet.buffer, 33, pipe) == NULL) {
+ fprintf(stderr, "server: failed to read digest\n");
+ exit(1);
+ }
+ if (strlen(temp_packet.buffer) != 32) {
+ fprintf(stderr, "server: digest was not 32 chars?? [%s]\n",
+ temp_packet.buffer);
+ exit(1);
+ }
+
+ fclose(pipe);
+
+ return temp_packet.buffer;
+}
+
+void packet_to_prompt(pamc_bp_t *prompt_p, __u8 control,
+ struct internal_packet *packet)
+{
+ PAM_BP_RENEW(prompt_p, control, packet->at);
+ PAM_BP_FILL(*prompt_p, 0, packet->at, packet->buffer);
+ packet->at = 0;
+}
+
+void prompt_to_packet(pamc_bp_t prompt, struct internal_packet *packet)
+{
+ int data_length;
+
+ data_length = PAM_BP_LENGTH(prompt);
+ packet->at = 0;
+ append_data(packet, data_length, NULL);
+ PAM_BP_EXTRACT(prompt, 0, data_length, packet->buffer);
+}
+
+int main(int argc, char **argv)
+{
+ pamc_handle_t pch;
+ pamc_bp_t prompt = NULL;
+ struct internal_packet packet_data, *packet;
+ char *temp_string, *secret, *user, *a_cookie, *seqid, *digest;
+ const char *cookie = "123451234512345";
+ int retval;
+
+ packet = &packet_data;
+ packet->length = 0;
+ packet->at = 0;
+ packet->buffer = NULL;
+
+ pch = pamc_start();
+ if (pch == NULL) {
+ fprintf(stderr, "server: unable to get a handle from libpamc\n");
+ exit(1);
+ }
+
+ temp_string = getlogin();
+ if (temp_string == NULL) {
+ fprintf(stderr, "server: who are you?\n");
+ exit(1);
+ }
+#define DOMAIN "@local.host"
+ user = malloc(1+strlen(temp_string)+strlen(DOMAIN));
+ if (user == NULL) {
+ fprintf(stderr, "server: out of memory for user id\n");
+ exit(1);
+ }
+ sprintf(user, "%s%s", temp_string, DOMAIN);
+
+ append_string(packet, "secret@here/", 0);
+ append_string(packet, user, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, cookie, 0);
+ packet_to_prompt(&prompt, PAM_BPC_SELECT, packet);
+
+ /* get the library to accept the first packet (which should load
+ the secret@here agent) */
+
+ retval = pamc_converse(pch, &prompt);
+ fprintf(stderr, "server: after conversation\n");
+ if (PAM_BP_CONTROL(prompt) != PAM_BPC_OK) {
+ fprintf(stderr, "server: prompt had unexpected control type: %u\n",
+ PAM_BP_CONTROL(prompt));
+ exit(1);
+ }
+
+ fprintf(stderr, "server: got a prompt back\n");
+
+ prompt_to_packet(prompt, packet);
+
+ temp_string = strtok(packet->buffer, "|");
+ if (temp_string == NULL) {
+ fprintf(stderr, "server: prompt does not contain anything");
+ exit(1);
+ }
+ seqid = strdup(temp_string);
+ if (seqid == NULL) {
+ fprintf(stderr, "server: unable to store sequence id\n");
+ }
+
+ temp_string = strtok(NULL, "|");
+ if (temp_string == NULL) {
+ fprintf(stderr, "server: no cookie from agent\n");
+ exit(1);
+ }
+ a_cookie = strdup(temp_string);
+ if (a_cookie == NULL) {
+ fprintf(stderr, "server: no memory to store agent cookie\n");
+ exit(1);
+ }
+
+ fprintf(stderr, "server: agent responded with {%s|%s}\n", seqid, a_cookie);
+ secret = identify_secret(user);
+ fprintf(stderr, "server: secret=%s\n", secret);
+
+ /* now, we construct the response */
+ packet->at = 0;
+ append_string(packet, a_cookie, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, cookie, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, secret, 0);
+
+ fprintf(stderr, "server: get digest of %s\n", packet->buffer);
+
+ digest = create_digest(packet->at, packet->buffer);
+
+ fprintf(stderr, "server: secret=%s, digest=%s\n", secret, digest);
+
+ packet->at = 0;
+ append_string(packet, seqid, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, digest, 0);
+ packet_to_prompt(&prompt, PAM_BPC_OK, packet);
+
+ retval = pamc_converse(pch, &prompt);
+ fprintf(stderr, "server: after 2nd conversation\n");
+ if (PAM_BP_CONTROL(prompt) != PAM_BPC_DONE) {
+ fprintf(stderr, "server: 2nd prompt had unexpected control type: %u\n",
+ PAM_BP_CONTROL(prompt));
+ exit(1);
+ }
+
+ prompt_to_packet(prompt, packet);
+ PAM_BP_RENEW(&prompt, 0, 0);
+
+ temp_string = strtok(packet->buffer, "|");
+ if (temp_string == NULL) {
+ fprintf(stderr, "no digest from agent\n");
+ exit(1);
+ }
+ temp_string = strdup(temp_string);
+
+ packet->at = 0;
+ append_string(packet, secret, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, cookie, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, a_cookie, 0);
+
+ fprintf(stderr, "server: get digest of %s\n", packet->buffer);
+
+ digest = create_digest(packet->at, packet->buffer);
+
+ fprintf(stderr, "server: digest=%s\n", digest);
+
+ if (strcmp(digest, temp_string)) {
+ fprintf(stderr, "server: agent doesn't know the secret\n");
+ fprintf(stderr, "server: agent says: [%s]\n"
+ "server: server says: [%s]\n", temp_string, digest);
+ exit(1);
+ } else {
+ fprintf(stderr, "server: agent seems to know the secret\n");
+
+ packet->at = 0;
+ append_string(packet, cookie, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, secret, 0);
+ append_string(packet, "|", 0);
+ append_string(packet, a_cookie, 0);
+
+ digest = create_digest(packet->at, packet->buffer);
+
+ fprintf(stderr, "server: putenv(\"AUTH_SESSION_TICKET=%s\")\n",
+ digest);
+ }
+
+
+ retval = pamc_end(&pch);
+
+ fprintf(stderr, "server: agent(s) were %shappy to terminate\n",
+ retval == PAM_BPC_TRUE ? "":"un");
+
+ exit(!retval);
+}
diff --git a/libpamc/test/regress/test.secret@here b/libpamc/test/regress/test.secret@here
new file mode 100755
index 00000000..2e0b9b94
--- /dev/null
+++ b/libpamc/test/regress/test.secret@here
@@ -0,0 +1,152 @@
+#!/usr/bin/perl
+
+##
+## this is a test script for regressing changes to the secret@here PAM
+## agent
+##
+
+$^W = 1;
+use strict;
+use IPC::Open2;
+
+$| = 1;
+
+my $whoami = `/usr/bin/whoami`; chomp $whoami;
+my $cookie = "12345";
+my $user_domain = "$whoami\@local.host";
+
+my $pid = open2(\*Reader, \*Writer, "../agents/secret\@here blah")
+ or die "failed to load secret\@here agent";
+
+unless (-f (getpwuid($<))[7]."/.secret\@here") {
+ print STDERR "server: ". "no " .(getpwuid($<))[7]. "/.secret\@here file\n";
+ die "no config file";
+}
+
+WriteBinaryPrompt(\*Writer, 0x02, "secret\@here/$user_domain|$cookie");
+
+my ($control, $data) = ReadBinaryPrompt(\*Reader);
+
+print STDERR "server: ". "reply: control=$control, data=$data\n";
+if ($control != 1) {
+ die "expected 1 (OK) for the first agent reply; got $control";
+}
+my ($seqid, $a_cookie) = split '\|', $data;
+
+# server needs to convince agent that it knows the secret before
+# agent will give a valid response
+my $secret = IdentifyLocalSecret($user_domain);
+my $digest = CreateDigest($a_cookie."|".$cookie."|".$secret);
+
+print STDERR "server: ". "digest = $digest\n";
+WriteBinaryPrompt(\*Writer, 0x01, "$seqid|$digest");
+
+# The agent will authenticate us and then reply with its
+# authenticating digest. we check that before we're done.
+
+($control, $data) = ReadBinaryPrompt(\*Reader);
+if ($control != 0x03) {
+ die "server: agent did not reply with a 'done' prompt ($control)\n";
+}
+
+unless ($data eq CreateDigest($secret."|".$cookie."|".$a_cookie)) {
+ die "server: agent is not authenticated\n";
+}
+
+print STDERR "server: agent appears to know secret\n";
+
+my $session_authenticated_ticket
+ = CreateDigest($cookie."|".$secret."|".$a_cookie);
+
+print STDERR "server: should putenv("
+ ."\"AUTH_SESSION_TICKET=$session_authenticated_ticket\")\n";
+
+exit 0;
+
+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;
+ print MD5in "$data";
+ close MD5in;
+ my $reply = <MD5out>;
+ ($reply) = split /\s/, $reply;
+ print STDERR "server: ". "md5 said: <$reply>\n";
+ close MD5out;
+
+ return $reply;
+}
+
+sub ReadBinaryPrompt ($) {
+ my ($fd) = @_;
+
+ my $buffer = " ";
+ my $count = read($fd, $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($fd, $buffer, $length)) {
+ $data .= $buffer;
+ if ($count != $length) {
+ $length -= $count;
+ next;
+ }
+
+ print STDERR "server: ". "data is [$data]\n";
+
+ return ($control, $data);
+ }
+
+ # broken packet data
+ return (-1, "");
+}
+
+sub WriteBinaryPrompt ($$$) {
+ my ($fd, $control, $data) = @_;
+
+ my $length = 5 + length($data);
+ printf STDERR "server: ". "{%d|0x%.2x|%s}\n", $length, $control, $data;
+ my $bp = pack("N C a*", $length, $control, $data);
+ print $fd $bp;
+
+ print STDERR "server: ". "control passed to agent\@here\n";
+}
+
+sub IdentifyLocalSecret ($) {
+ my ($identifier) = @_;
+ my $secret;
+
+ my $whoami = `/usr/bin/whoami` ; chomp $whoami;
+ 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;
+}
+