2 * $Id: pam_secret.c,v 1.2 2001/01/20 22:29:47 agmorgan Exp $
4 * Copyright (c) 1999 Andrew G. Morgan <morgan@linux.kernel.org>
8 * WARNING: AS WRITTEN THIS CODE IS NOT SECURE. THE MD5 IMPLEMENTATION
9 * NEEDS TO BE INTEGRATED MORE NATIVELY.
18 #include <sys/types.h>
21 #include <security/pam_modules.h>
22 #include <security/pam_client.h>
23 #include <security/_pam_macros.h>
26 * This is a sample module that demonstrates the use of binary prompts
27 * and how they can be used to implement sophisticated authentication
32 int retval; /* last retval returned by the authentication fn */
33 int state; /* what state the module was in when it
34 returned incomplete */
36 char *username; /* the name of the local user */
38 char server_cookie[33]; /* storage for 32 bytes of server cookie */
39 char client_cookie[33]; /* storage for 32 bytes of client cookie */
41 char *secret_data; /* pointer to <NUL> terminated secret_data */
42 int invalid_secret; /* indication of whether the secret is valid */
44 pamc_bp_t current_prompt; /* place to store the current prompt */
45 pamc_bp_t current_reply; /* place to receive the reply prompt */
48 #define PS_STATE_ID "PAM_SECRET__STATE"
49 #define PS_AGENT_ID "secret@here"
50 #define PS_STATE_DEAD 0
51 #define PS_STATE_INIT 1
52 #define PS_STATE_PROMPT1 2
53 #define PS_STATE_PROMPT2 3
55 #define MAX_LEN_HOSTNAME 512
56 #define MAX_FILE_LINE_LEN 1024
59 * Routine for generating 16*8 bits of random data represented in ASCII hex
62 static int generate_cookie(unsigned char *buffer_33)
64 static const char hexarray[] = "0123456789abcdef";
67 /* fill buffer_33 with 32 hex characters (lower case) + '\0' */
68 fd = open("/dev/urandom", O_RDONLY);
70 D(("failed to open /dev/urandom"));
73 read(fd, buffer_33 + 16, 16);
76 /* expand top 16 bytes into 32 nibbles */
77 for (i=0; i<16; ++i) {
78 buffer_33[2*i ] = hexarray[(buffer_33[16+i] & 0xf0)>>4];
79 buffer_33[2*i+1] = hexarray[(buffer_33[16+i] & 0x0f)];
88 * XXX - This is a hack, and is fundamentally insecure. Its subject to
89 * all sorts of attacks not to mention the fact that all our secrets
90 * will be displayed on the command line for someone doing 'ps' to
91 * see. This is just for programming convenience in this instance, it
92 * needs to be replaced with the md5 code. Although I am loath to
93 * add yet another instance of md5 code to the Linux-PAM source code.
94 * [Need to think of a cleaner way to do this for the distribution as
98 #define COMMAND_FORMAT "/bin/echo -n '%s|%s|%s'|/usr/bin/md5sum -"
100 int create_digest(const char *d1, const char *d2, const char *d3,
107 length = strlen(d1)+strlen(d2)+strlen(d3)+sizeof(COMMAND_FORMAT);
108 buffer = malloc(length);
109 if (buffer == NULL) {
110 D(("out of memory"));
114 sprintf(buffer, COMMAND_FORMAT, d1,d2,d3);
116 D(("executing pipe [%s]", buffer));
117 pipe = popen(buffer, "r");
118 memset(buffer, 0, length);
122 D(("failed to launch pipe"));
126 if (fgets(buffer_33, 33, pipe) == NULL) {
127 D(("failed to read digest"));
131 if (strlen(buffer_33) != 32) {
132 D(("digest was not 32 chars"));
138 D(("done [%s]", buffer_33));
144 * method to attempt to instruct the application's conversation function
147 static int converse(pam_handle_t *pamh, struct ps_state_s *new)
150 struct pam_conv *conv;
154 retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
155 if (retval == PAM_SUCCESS) {
156 struct pam_message msg;
157 struct pam_response *single_reply;
158 const struct pam_message *msg_ptr;
160 memset(&msg, 0, sizeof(msg));
161 msg.msg_style = PAM_BINARY_PROMPT;
162 msg.msg = (const char *) new->current_prompt;
166 retval = conv->conv(1, &msg_ptr, &single_reply, conv->appdata_ptr);
167 if (retval == PAM_SUCCESS) {
168 if ((single_reply == NULL) || (single_reply->resp == NULL)) {
169 retval == PAM_CONV_ERR;
171 new->current_reply = (pamc_bp_t) single_reply->resp;
172 single_reply->resp = NULL;
182 if (retval == PAM_SUCCESS) {
183 D(("reply has length=%d and control=%u",
184 PAM_BP_LENGTH(new->current_reply),
185 PAM_BP_CONTROL(new->current_reply)));
187 D(("returning %s", pam_strerror(pamh, retval)));
194 * identify the secret in question
197 #define SECRET_FILE_FORMAT "%s/.secret@here"
199 char *identify_secret(char *identity, const char *user)
206 pwd = getpwnam(user);
207 if ((pwd == NULL) || (pwd->pw_dir == NULL)) {
208 D(("user [%s] is not known", user));
211 length_id = strlen(pwd->pw_dir) + sizeof(SECRET_FILE_FORMAT);
212 temp = malloc(length_id);
214 D(("out of memory"));
219 sprintf(temp, SECRET_FILE_FORMAT, pwd->pw_dir);
222 D(("opening key file [%s]", temp));
223 secrets = fopen(temp, "r");
224 memset(temp, 0, length_id);
226 if (secrets == NULL) {
227 D(("failed to open key file"));
231 length_id = strlen(identity);
232 temp = malloc(MAX_FILE_LINE_LEN);
237 if (fgets(temp, MAX_FILE_LINE_LEN, secrets) == NULL) {
242 D(("cf[%s][%s]", identity, temp));
243 if (memcmp(temp, identity, length_id)) {
250 for (secret=temp+length_id; *secret; ++secret) {
251 if (!(*secret == ' ' || *secret == '\n' || *secret == '\t')) {
256 memmove(temp, secret, MAX_FILE_LINE_LEN-(secret-(temp+length_id)));
259 for (; *secret; ++secret) {
260 if (*secret == ' ' || *secret == '\n' || *secret == '\t') {
269 D(("secret found [%s]", temp));
278 * function to perform the two message authentication process
279 * (with support for event driven conversation functions)
282 static int auth_sequence(pam_handle_t *pamh,
283 const struct ps_state_s *old, struct ps_state_s *new)
285 const char *rhostname;
286 const char *rusername;
289 retval = pam_get_item(pamh, PAM_RUSER, (const void **) &rusername);
290 if ((retval != PAM_SUCCESS) || (rusername == NULL)) {
291 D(("failed to obtain an rusername"));
292 new->state = PS_STATE_DEAD;
296 retval = pam_get_item(pamh, PAM_RHOST, (const void **) &rhostname);
297 if ((retval != PAM_SUCCESS) || (rhostname == NULL)) {
298 D(("failed to identify local hostname: ", pam_strerror(pamh, retval)));
299 new->state = PS_STATE_DEAD;
303 D(("switch on new->state=%d [%s@%s]", new->state, rusername, rhostname));
304 switch (new->state) {
308 const char *user = NULL;
310 retval = pam_get_user(pamh, &user, NULL);
312 if ((retval == PAM_SUCCESS) && (user == NULL)) {
313 D(("success but no username?"));
314 new->state = PS_STATE_DEAD;
315 retval = PAM_USER_UNKNOWN;
318 if (retval != PAM_SUCCESS) {
319 if (retval == PAM_CONV_AGAIN) {
320 retval = PAM_INCOMPLETE;
322 new->state = PS_STATE_DEAD;
324 D(("state init failed: %s", pam_strerror(pamh, retval)));
328 /* nothing else in this 'case' can be retried */
330 new->username = strdup(user);
331 if (new->username == NULL) {
332 D(("out of memory"));
333 new->state = PS_STATE_DEAD;
337 if (! generate_cookie(new->server_cookie)) {
338 D(("problem generating server cookie"));
339 new->state = PS_STATE_DEAD;
343 new->current_prompt = NULL;
344 PAM_BP_RENEW(&new->current_prompt, PAM_BPC_SELECT,
345 sizeof(PS_AGENT_ID) + strlen(rusername) + 1
346 + strlen(rhostname) + 1 + 32);
347 sprintf(PAM_BP_WDATA(new->current_prompt),
348 PS_AGENT_ID "/%s@%s|%.32s", rusername, rhostname,
351 /* note, the BP is guaranteed by the spec to be <NUL> terminated */
352 D(("initialization packet [%s]", PAM_BP_DATA(new->current_prompt)));
355 new->state = PS_STATE_PROMPT1;
357 D(("fall through to state_prompt1"));
360 case PS_STATE_PROMPT1:
364 /* send {secret@here/jdoe@client.host|<s_cookie>} */
365 retval = converse(pamh, new);
366 if (retval != PAM_SUCCESS) {
367 if (retval == PAM_CONV_AGAIN) {
368 D(("conversation failed to complete"));
369 return PAM_INCOMPLETE;
371 new->state = PS_STATE_DEAD;
376 if (retval != PAM_SUCCESS) {
377 D(("failed to read ruser@rhost"));
378 new->state = PS_STATE_DEAD;
382 /* expect to receive the following {<seqid>|<a_cookie>} */
383 if (new->current_reply == NULL) {
384 D(("converstation returned [%s] but gave no reply",
385 pam_strerror(pamh, retval)));
386 new->state = PS_STATE_DEAD;
391 length = PAM_BP_LENGTH(new->current_reply);
392 for (i=0; i<length; ++i) {
393 if (PAM_BP_RDATA(new->current_reply)[i] == '|') {
398 D(("malformed response (no |) of length %d", length));
399 new->state = PS_STATE_DEAD;
402 if ((length - ++i) != 32) {
403 D(("cookie is incorrect length (%d,%d) %d != 32",
404 length, i, length-i));
405 new->state = PS_STATE_DEAD;
409 /* copy client cookie */
410 memcpy(new->client_cookie, PAM_BP_RDATA(new->current_reply)+i, 32);
412 /* generate a prompt that is length(seqid) + length(|) + 32 long */
413 PAM_BP_RENEW(&new->current_prompt, PAM_BPC_OK, i+32);
414 /* copy the head of the response prompt */
415 memcpy(PAM_BP_WDATA(new->current_prompt),
416 PAM_BP_RDATA(new->current_reply), i);
417 PAM_BP_RENEW(&new->current_reply, 0, 0);
419 /* look up the secret */
420 new->invalid_secret = 0;
422 if (new->secret_data == NULL) {
425 ruser_rhost = malloc(strlen(rusername)+2+strlen(rhostname));
426 if (ruser_rhost == NULL) {
427 D(("out of memory"));
428 new->state = PS_STATE_DEAD;
431 sprintf(ruser_rhost, "%s@%s", rusername, rhostname);
432 new->secret_data = identify_secret(ruser_rhost, new->username);
434 memset(ruser_rhost, 0, strlen(ruser_rhost));
438 if (new->secret_data == NULL) {
439 D(("secret not found for user"));
440 new->invalid_secret = 1;
442 /* need to make up a secret */
443 new->secret_data = malloc(32 + 1);
444 if (new->secret_data == NULL) {
445 D(("out of memory"));
446 new->state = PS_STATE_DEAD;
449 if (! generate_cookie(new->secret_data)) {
450 D(("what's up - no fake cookie generated?"));
451 new->state = PS_STATE_DEAD;
456 /* construct md5[<client_cookie>|<server_cookie>|<secret_data>] */
457 if (! create_digest(new->client_cookie, new->server_cookie,
459 PAM_BP_WDATA(new->current_prompt)+i)) {
460 D(("md5 digesting failed"));
461 new->state = PS_STATE_DEAD;
465 /* prompt2 is now constructed - fall through to send it */
468 case PS_STATE_PROMPT2:
470 /* send {<seqid>|md5[<client_cookie>|<server_cookie>|<secret_data>]} */
471 retval = converse(pamh, new);
472 if (retval != PAM_SUCCESS) {
473 if (retval == PAM_CONV_AGAIN) {
474 D(("conversation failed to complete"));
475 return PAM_INCOMPLETE;
477 new->state = PS_STATE_DEAD;
482 /* After we complete this section, we should not be able to
483 recall this authentication function. So, we force all
484 future calls into the weeds. */
486 new->state = PS_STATE_DEAD;
488 /* expect reply:{md5[<secret_data>|<server_cookie>|<client_cookie>]} */
492 char expectation[33];
494 if (!create_digest(new->secret_data, new->server_cookie,
495 new->client_cookie, expectation)) {
496 new->state = PS_STATE_DEAD;
500 cf = strcmp(expectation, PAM_BP_RDATA(new->current_reply));
501 memset(expectation, 0, sizeof(expectation));
502 if (cf || new->invalid_secret) {
503 D(("failed to authenticate"));
508 D(("correctly authenticated :)"));
513 new->state = PS_STATE_DEAD;
517 D(("state is currently dead/unknown"));
521 fprintf(stderr, "pam_secret: this should not be reached\n");
525 static void clean_data(pam_handle_t *pamh, void *datum, int error_status)
527 struct ps_state_s *data = datum;
529 D(("liberating datum=%p", datum));
533 PAM_BP_RENEW(&data->current_prompt, 0, 0);
535 PAM_BP_RENEW(&data->current_reply, 0, 0);
536 D(("overwrite datum"));
537 memset(data, 0, sizeof(struct ps_state_s));
538 D(("liberate datum"));
546 * front end for the authentication function
549 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
550 int argc, const char **argv)
553 struct ps_state_s *new_data;
554 const struct ps_state_s *old_data;
558 new_data = calloc(1, sizeof(struct ps_state_s));
559 if (new_data == NULL) {
560 D(("out of memory"));
563 new_data->retval = PAM_SUCCESS;
565 retval = pam_get_data(pamh, PS_STATE_ID, (const void **) &old_data);
566 if (retval == PAM_SUCCESS) {
567 new_data->state = old_data->state;
568 memcpy(new_data->server_cookie, old_data->server_cookie, 32);
569 memcpy(new_data->client_cookie, old_data->client_cookie, 32);
570 if (old_data->username) {
571 new_data->username = strdup(old_data->username);
573 if (old_data->secret_data) {
574 new_data->secret_data = strdup(old_data->secret_data);
576 if (old_data->current_prompt) {
579 length = PAM_BP_LENGTH(old_data->current_prompt);
580 PAM_BP_RENEW(&new_data->current_prompt,
581 PAM_BP_CONTROL(old_data->current_prompt), length);
582 PAM_BP_FILL(new_data->current_prompt, 0, length,
583 PAM_BP_RDATA(old_data->current_prompt));
585 /* don't need to duplicate current_reply */
588 new_data->state = PS_STATE_INIT;
591 D(("call auth_sequence"));
592 new_data->retval = auth_sequence(pamh, old_data, new_data);
593 D(("returned from auth_sequence"));
595 retval = pam_set_data(pamh, PS_STATE_ID, new_data, clean_data);
596 if (retval != PAM_SUCCESS) {
597 D(("unable to store new_data"));
599 retval = new_data->retval;
602 old_data = new_data = NULL;
604 D(("done (%d)", retval));
609 * front end for the credential setting function
612 #define AUTH_SESSION_TICKET_ENV_FORMAT "AUTH_SESSION_TICKET="
614 int pam_sm_setcred(pam_handle_t *pamh, int flags,
615 int argc, const char **argv)
618 const struct ps_state_s *old_data;
622 /* XXX - need to pay attention to the various flavors of call */
624 /* XXX - need provide an option to turn this feature on/off: if
625 other modules want to supply an AUTH_SESSION_TICKET, we should
626 leave it up to the admin which module dominiates. */
628 retval = pam_get_data(pamh, PS_STATE_ID, (const void **) &old_data);
629 if (retval != PAM_SUCCESS) {
630 D(("no data to base decision on"));
635 * If ok, export a derived shared secret session ticket to the
636 * client's PAM environment - the ticket has the form
638 * AUTH_SESSION_TICKET =
639 * md5[<server_cookie>|<secret_data>|<client_cookie>]
641 * This is a precursor to supporting a spoof resistant trusted
642 * path mechanism. This shared secret ticket can be used to add
643 * a hard-to-guess checksum to further authentication data.
646 retval = old_data->retval;
647 if (retval == PAM_SUCCESS) {
648 char envticket[sizeof(AUTH_SESSION_TICKET_ENV_FORMAT)+32];
650 memcpy(envticket, AUTH_SESSION_TICKET_ENV_FORMAT,
651 sizeof(AUTH_SESSION_TICKET_ENV_FORMAT));
653 if (! create_digest(old_data->server_cookie, old_data->secret_data,
654 old_data->client_cookie,
655 envticket+sizeof(AUTH_SESSION_TICKET_ENV_FORMAT)-1
657 D(("unable to generate a digest for session ticket"));
661 D(("putenv[%s]", envticket));
662 retval = pam_putenv(pamh, envticket);
663 memset(envticket, 0, sizeof(envticket));
667 D(("done (%d)", retval));