Import Linux-PAM.
[profile/ivi/pam.git] / libpamc / test / modules / pam_secret.c
1 /*
2  * $Id$
3  *
4  * Copyright (c) 1999 Andrew G. Morgan <morgan@linux.kernel.org>
5  */
6
7 /*
8  * WARNING: AS WRITTEN THIS CODE IS NOT SECURE. THE MD5 IMPLEMENTATION
9  *          NEEDS TO BE INTEGRATED MORE NATIVELY.
10  */
11
12 #include <fcntl.h>
13 #include <pwd.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <sys/types.h>
17 #include <sys/stat.h>
18
19 #include <security/pam_modules.h>
20 #include <security/pam_client.h>
21 #include <security/_pam_macros.h>
22
23 /*
24  * This is a sample module that demonstrates the use of binary prompts
25  * and how they can be used to implement sophisticated authentication
26  * schemes.
27  */
28
29 struct ps_state_s {
30     int retval;        /* last retval returned by the authentication fn */
31     int state;         /* what state the module was in when it
32                           returned incomplete */
33
34     char *username;    /* the name of the local user */
35
36     char server_cookie[33]; /* storage for 32 bytes of server cookie */
37     char client_cookie[33]; /* storage for 32 bytes of client cookie */
38
39     char *secret_data; /* pointer to <NUL> terminated secret_data */
40     int invalid_secret;  /* indication of whether the secret is valid */
41
42     pamc_bp_t current_prompt;    /* place to store the current prompt */
43     pamc_bp_t current_reply;     /* place to receive the reply prompt */
44 };
45
46 #define PS_STATE_ID   "PAM_SECRET__STATE"
47 #define PS_AGENT_ID   "secret@here"
48 #define PS_STATE_DEAD          0
49 #define PS_STATE_INIT          1
50 #define PS_STATE_PROMPT1       2
51 #define PS_STATE_PROMPT2       3
52
53 #define MAX_LEN_HOSTNAME       512
54 #define MAX_FILE_LINE_LEN      1024
55
56 /*
57  * Routine for generating 16*8 bits of random data represented in ASCII hex
58  */
59
60 static int generate_cookie(unsigned char *buffer_33)
61 {
62     static const char hexarray[] = "0123456789abcdef";
63     int i, fd;
64
65     /* fill buffer_33 with 32 hex characters (lower case) + '\0' */
66     fd = open("/dev/urandom", O_RDONLY);
67     if (fd < 0) {
68         D(("failed to open /dev/urandom"));
69         return 0;
70     }
71     read(fd, buffer_33 + 16, 16);
72     close(fd);
73
74     /* expand top 16 bytes into 32 nibbles */
75     for (i=0; i<16; ++i) {
76         buffer_33[2*i  ] = hexarray[(buffer_33[16+i] & 0xf0)>>4];
77         buffer_33[2*i+1] = hexarray[(buffer_33[16+i] & 0x0f)];
78     }
79
80     buffer_33[32] = '\0';
81
82     return 1;
83 }
84
85 /*
86  * XXX - This is a hack, and is fundamentally insecure. Its subject to
87  * all sorts of attacks not to mention the fact that all our secrets
88  * will be displayed on the command line for someone doing 'ps' to
89  * see. This is just for programming convenience in this instance, it
90  * needs to be replaced with the md5 code. Although I am loath to
91  * add yet another instance of md5 code to the Linux-PAM source code.
92  * [Need to think of a cleaner way to do this for the distribution as
93  * a whole...]
94  */
95
96 #define COMMAND_FORMAT "/bin/echo -n '%s|%s|%s'|/usr/bin/md5sum -"
97
98 int create_digest(const char *d1, const char *d2, const char *d3,
99                   char *buffer_33)
100 {
101     int length;
102     char *buffer;
103     FILE *pipe;
104
105     length = strlen(d1)+strlen(d2)+strlen(d3)+sizeof(COMMAND_FORMAT);
106     buffer = malloc(length);
107     if (buffer == NULL) {
108         D(("out of memory"));
109         return 0;
110     }
111
112     sprintf(buffer, COMMAND_FORMAT, d1,d2,d3);
113
114     D(("executing pipe [%s]", buffer));
115     pipe = popen(buffer, "r");
116     memset(buffer, 0, length);
117     free(buffer);
118
119     if (pipe == NULL) {
120         D(("failed to launch pipe"));
121         return 0;
122     }
123
124     if (fgets(buffer_33, 33, pipe) == NULL) {
125         D(("failed to read digest"));
126         return 0;
127     }
128
129     if (strlen(buffer_33) != 32) {
130         D(("digest was not 32 chars"));
131         return 0;
132     }
133
134     fclose(pipe);
135
136     D(("done [%s]", buffer_33));
137
138     return 1;
139 }
140
141 /*
142  * method to attempt to instruct the application's conversation function
143  */
144
145 static int converse(pam_handle_t *pamh, struct ps_state_s *new)
146 {
147     int retval;
148     struct pam_conv *conv;
149
150     D(("called"));
151
152     retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
153     if (retval == PAM_SUCCESS) {
154         struct pam_message msg;
155         struct pam_response *single_reply;
156         const struct pam_message *msg_ptr;
157
158         memset(&msg, 0, sizeof(msg));
159         msg.msg_style = PAM_BINARY_PROMPT;
160         msg.msg = (const char *) new->current_prompt;
161         msg_ptr = &msg;
162
163         single_reply = NULL;
164         retval = conv->conv(1, &msg_ptr, &single_reply, conv->appdata_ptr);
165         if (retval == PAM_SUCCESS) {
166             if ((single_reply == NULL) || (single_reply->resp == NULL)) {
167                 retval == PAM_CONV_ERR;
168             } else {
169                 new->current_reply = (pamc_bp_t) single_reply->resp;
170                 single_reply->resp = NULL;
171             }
172         }
173
174         if (single_reply) {
175             free(single_reply);
176         }
177     }
178
179 #ifdef PAM_DEBUG
180     if (retval == PAM_SUCCESS) {
181         D(("reply has length=%d and control=%u",
182            PAM_BP_LENGTH(new->current_reply),
183            PAM_BP_CONTROL(new->current_reply)));
184     }
185     D(("returning %s", pam_strerror(pamh, retval)));
186 #endif
187
188     return retval;
189 }
190
191 /*
192  * identify the secret in question
193  */
194
195 #define SECRET_FILE_FORMAT "%s/.secret@here"
196
197 char *identify_secret(char *identity, const char *user)
198 {
199     struct passwd *pwd;
200     char *temp;
201     FILE *secrets;
202     int length_id;
203
204     pwd = getpwnam(user);
205     if ((pwd == NULL) || (pwd->pw_dir == NULL)) {
206         D(("user [%s] is not known", user));
207         return NULL;
208     }
209
210     length_id = strlen(pwd->pw_dir) + sizeof(SECRET_FILE_FORMAT);
211     temp = malloc(length_id);
212     if (temp == NULL) {
213         D(("out of memory"));
214         pwd = NULL;
215         return NULL;
216     }
217
218     sprintf(temp, SECRET_FILE_FORMAT, pwd->pw_dir);
219     pwd = NULL;
220
221     D(("opening key file [%s]", temp));
222     secrets = fopen(temp, "r");
223     memset(temp, 0, length_id);
224
225     if (secrets == NULL) {
226         D(("failed to open key file"));
227         return NULL;
228     }
229
230     length_id = strlen(identity);
231     temp = malloc(MAX_FILE_LINE_LEN);
232
233     for (;;) {
234         char *secret = NULL;
235
236         if (fgets(temp, MAX_FILE_LINE_LEN, secrets) == NULL) {
237             fclose(secrets);
238             return NULL;
239         }
240
241         D(("cf[%s][%s]", identity, temp));
242         if (memcmp(temp, identity, length_id)) {
243             continue;
244         }
245
246         D(("found entry"));
247         fclose(secrets);
248
249         for (secret=temp+length_id; *secret; ++secret) {
250             if (!(*secret == ' ' || *secret == '\n' || *secret == '\t')) {
251                 break;
252             }
253         }
254
255         memmove(temp, secret, MAX_FILE_LINE_LEN-(secret-(temp+length_id)));
256         secret = temp;
257
258         for (; *secret; ++secret) {
259             if (*secret == ' ' || *secret == '\n' || *secret == '\t') {
260                 break;
261             }
262         }
263
264         if (*secret) {
265             *secret = '\0';
266         }
267
268         D(("secret found [%s]", temp));
269
270         return temp;
271     }
272
273     /* NOT REACHED */
274 }
275
276 /*
277  * function to perform the two message authentication process
278  * (with support for event driven conversation functions)
279  */
280
281 static int auth_sequence(pam_handle_t *pamh,
282                          const struct ps_state_s *old, struct ps_state_s *new)
283 {
284     const char *rhostname;
285     const char *rusername;
286     int retval;
287
288     retval = pam_get_item(pamh, PAM_RUSER, (const void **) &rusername);
289     if ((retval != PAM_SUCCESS) || (rusername == NULL)) {
290         D(("failed to obtain an rusername"));
291         new->state = PS_STATE_DEAD;
292         return PAM_AUTH_ERR;
293     }
294
295     retval = pam_get_item(pamh, PAM_RHOST, (const void **) &rhostname);
296     if ((retval != PAM_SUCCESS) || (rhostname == NULL)) {
297         D(("failed to identify local hostname: ", pam_strerror(pamh, retval)));
298         new->state = PS_STATE_DEAD;
299         return PAM_AUTH_ERR;
300     }
301
302     D(("switch on new->state=%d [%s@%s]", new->state, rusername, rhostname));
303     switch (new->state) {
304
305     case PS_STATE_INIT:
306     {
307         const char *user = NULL;
308
309         retval = pam_get_user(pamh, &user, NULL);
310
311         if ((retval == PAM_SUCCESS) && (user == NULL)) {
312             D(("success but no username?"));
313             new->state = PS_STATE_DEAD;
314             retval = PAM_USER_UNKNOWN;
315         }
316
317         if (retval != PAM_SUCCESS) {
318             if (retval == PAM_CONV_AGAIN) {
319                 retval = PAM_INCOMPLETE;
320             } else {
321                 new->state = PS_STATE_DEAD;
322             }
323             D(("state init failed: %s", pam_strerror(pamh, retval)));
324             return retval;
325         }
326
327         /* nothing else in this 'case' can be retried */
328
329         new->username = strdup(user);
330         if (new->username == NULL) {
331             D(("out of memory"));
332             new->state = PS_STATE_DEAD;
333             return PAM_BUF_ERR;
334         }
335
336         if (! generate_cookie(new->server_cookie)) {
337             D(("problem generating server cookie"));
338             new->state = PS_STATE_DEAD;
339             return PAM_ABORT;
340         }
341
342         new->current_prompt = NULL;
343         PAM_BP_RENEW(&new->current_prompt, PAM_BPC_SELECT,
344                      sizeof(PS_AGENT_ID) + strlen(rusername) + 1
345                      + strlen(rhostname) + 1 + 32);
346         sprintf(PAM_BP_WDATA(new->current_prompt),
347                 PS_AGENT_ID "/%s@%s|%.32s", rusername, rhostname,
348                 new->server_cookie);
349
350         /* note, the BP is guaranteed by the spec to be <NUL> terminated */
351         D(("initialization packet [%s]", PAM_BP_DATA(new->current_prompt)));
352
353         /* fall through */
354         new->state = PS_STATE_PROMPT1;
355
356         D(("fall through to state_prompt1"));
357     }
358
359     case PS_STATE_PROMPT1:
360     {
361         int i, length;
362
363         /* send {secret@here/jdoe@client.host|<s_cookie>} */
364         retval = converse(pamh, new);
365         if (retval != PAM_SUCCESS) {
366             if (retval == PAM_CONV_AGAIN) {
367                 D(("conversation failed to complete"));
368                 return PAM_INCOMPLETE;
369             } else {
370                 new->state = PS_STATE_DEAD;
371                 return retval;
372             }
373         }
374
375         if (retval != PAM_SUCCESS) {
376             D(("failed to read ruser@rhost"));
377             new->state = PS_STATE_DEAD;
378             return PAM_AUTH_ERR;
379         }
380
381         /* expect to receive the following {<seqid>|<a_cookie>} */
382         if (new->current_reply == NULL) {
383             D(("converstation returned [%s] but gave no reply",
384                pam_strerror(pamh, retval)));
385             new->state = PS_STATE_DEAD;
386             return PAM_CONV_ERR;
387         }
388
389         /* find | */
390         length = PAM_BP_LENGTH(new->current_reply);
391         for (i=0; i<length; ++i) {
392             if (PAM_BP_RDATA(new->current_reply)[i] == '|') {
393                 break;
394             }
395         }
396         if (i >= length) {
397             D(("malformed response (no |) of length %d", length));
398             new->state = PS_STATE_DEAD;
399             return PAM_CONV_ERR;
400         }
401         if ((length - ++i) != 32) {
402             D(("cookie is incorrect length (%d,%d) %d != 32",
403                length, i, length-i));
404             new->state = PS_STATE_DEAD;
405             return PAM_CONV_ERR;
406         }
407
408         /* copy client cookie */
409         memcpy(new->client_cookie, PAM_BP_RDATA(new->current_reply)+i, 32);
410
411         /* generate a prompt that is length(seqid) + length(|) + 32 long */
412         PAM_BP_RENEW(&new->current_prompt, PAM_BPC_OK, i+32);
413         /* copy the head of the response prompt */
414         memcpy(PAM_BP_WDATA(new->current_prompt),
415                PAM_BP_RDATA(new->current_reply), i);
416         PAM_BP_RENEW(&new->current_reply, 0, 0);
417
418         /* look up the secret */
419         new->invalid_secret = 0;
420
421         if (new->secret_data == NULL) {
422             char *ruser_rhost;
423
424             ruser_rhost = malloc(strlen(rusername)+2+strlen(rhostname));
425             if (ruser_rhost == NULL) {
426                 D(("out of memory"));
427                 new->state = PS_STATE_DEAD;
428                 return PAM_BUF_ERR;
429             }
430             sprintf(ruser_rhost, "%s@%s", rusername, rhostname);
431             new->secret_data = identify_secret(ruser_rhost, new->username);
432
433             memset(ruser_rhost, 0, strlen(ruser_rhost));
434             free(ruser_rhost);
435         }
436
437         if (new->secret_data == NULL) {
438             D(("secret not found for user"));
439             new->invalid_secret = 1;
440
441             /* need to make up a secret */
442             new->secret_data = malloc(32 + 1);
443             if (new->secret_data == NULL) {
444                 D(("out of memory"));
445                 new->state = PS_STATE_DEAD;
446                 return PAM_BUF_ERR;
447             }
448             if (! generate_cookie(new->secret_data)) {
449                 D(("what's up - no fake cookie generated?"));
450                 new->state = PS_STATE_DEAD;
451                 return PAM_ABORT;
452             }
453         }
454
455         /* construct md5[<client_cookie>|<server_cookie>|<secret_data>] */
456         if (! create_digest(new->client_cookie, new->server_cookie,
457                             new->secret_data,
458                             PAM_BP_WDATA(new->current_prompt)+i)) {
459             D(("md5 digesting failed"));
460             new->state = PS_STATE_DEAD;
461             return PAM_ABORT;
462         }
463
464         /* prompt2 is now constructed - fall through to send it */
465     }
466
467     case PS_STATE_PROMPT2:
468     {
469         /* send {<seqid>|md5[<client_cookie>|<server_cookie>|<secret_data>]} */
470         retval = converse(pamh, new);
471         if (retval != PAM_SUCCESS) {
472             if (retval == PAM_CONV_AGAIN) {
473                 D(("conversation failed to complete"));
474                 return PAM_INCOMPLETE;
475             } else {
476                 new->state = PS_STATE_DEAD;
477                 return retval;
478             }
479         }
480
481         /* After we complete this section, we should not be able to
482            recall this authentication function. So, we force all
483            future calls into the weeds. */
484
485         new->state = PS_STATE_DEAD;
486
487         /* expect reply:{md5[<secret_data>|<server_cookie>|<client_cookie>]} */
488
489         {
490             int cf;
491             char expectation[33];
492
493             if (!create_digest(new->secret_data, new->server_cookie,
494                                new->client_cookie, expectation)) {
495                 new->state = PS_STATE_DEAD;
496                 return PAM_ABORT;
497             }
498
499             cf = strcmp(expectation, PAM_BP_RDATA(new->current_reply));
500             memset(expectation, 0, sizeof(expectation));
501             if (cf || new->invalid_secret) {
502                 D(("failed to authenticate"));
503                 return PAM_AUTH_ERR;
504             }
505         }
506
507         D(("correctly authenticated :)"));
508         return PAM_SUCCESS;
509     }
510
511     default:
512         new->state = PS_STATE_DEAD;
513
514     case PS_STATE_DEAD:
515
516         D(("state is currently dead/unknown"));
517         return PAM_AUTH_ERR;
518     }
519
520     fprintf(stderr, "pam_secret: this should not be reached\n");
521     return PAM_ABORT;
522 }
523
524 static void clean_data(pam_handle_t *pamh, void *datum, int error_status)
525 {
526     struct ps_state_s *data = datum;
527
528     D(("liberating datum=%p", datum));
529
530     if (data) {
531         D(("renew prompt"));
532         PAM_BP_RENEW(&data->current_prompt, 0, 0);
533         D(("renew reply"));
534         PAM_BP_RENEW(&data->current_reply, 0, 0);
535         D(("overwrite datum"));
536         memset(data, 0, sizeof(struct ps_state_s));
537         D(("liberate datum"));
538         free(data);
539     }
540
541     D(("done."));
542 }
543
544 /*
545  * front end for the authentication function
546  */
547
548 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
549                         int argc, const char **argv)
550 {
551     int retval;
552     struct ps_state_s *new_data;
553     const struct ps_state_s *old_data;
554
555     D(("called"));
556
557     new_data = calloc(1, sizeof(struct ps_state_s));
558     if (new_data == NULL) {
559         D(("out of memory"));
560         return PAM_BUF_ERR;
561     }
562     new_data->retval = PAM_SUCCESS;
563
564     retval = pam_get_data(pamh, PS_STATE_ID, (const void **) &old_data);
565     if (retval == PAM_SUCCESS) {
566         new_data->state = old_data->state;
567         memcpy(new_data->server_cookie, old_data->server_cookie, 32);
568         memcpy(new_data->client_cookie, old_data->client_cookie, 32);
569         if (old_data->username) {
570             new_data->username = strdup(old_data->username);
571         }
572         if (old_data->secret_data) {
573             new_data->secret_data = strdup(old_data->secret_data);
574         }
575         if (old_data->current_prompt) {
576             int length;
577
578             length = PAM_BP_LENGTH(old_data->current_prompt);
579             PAM_BP_RENEW(&new_data->current_prompt,
580                          PAM_BP_CONTROL(old_data->current_prompt), length);
581             PAM_BP_FILL(new_data->current_prompt, 0, length,
582                         PAM_BP_RDATA(old_data->current_prompt));
583         }
584         /* don't need to duplicate current_reply */
585     } else {
586         old_data = NULL;
587         new_data->state = PS_STATE_INIT;
588     }
589
590     D(("call auth_sequence"));
591     new_data->retval = auth_sequence(pamh, old_data, new_data);
592     D(("returned from auth_sequence"));
593
594     retval = pam_set_data(pamh, PS_STATE_ID, new_data, clean_data);
595     if (retval != PAM_SUCCESS) {
596         D(("unable to store new_data"));
597     } else {
598         retval = new_data->retval;
599     }
600
601     old_data = new_data = NULL;
602
603     D(("done (%d)", retval));
604     return retval;
605 }
606
607 /*
608  * front end for the credential setting function
609  */
610
611 #define AUTH_SESSION_TICKET_ENV_FORMAT "AUTH_SESSION_TICKET="
612
613 int pam_sm_setcred(pam_handle_t *pamh, int flags,
614                    int argc, const char **argv)
615 {
616     int retval;
617     const struct ps_state_s *old_data;
618
619     D(("called"));
620
621     /* XXX - need to pay attention to the various flavors of call */
622
623     /* XXX - need provide an option to turn this feature on/off: if
624        other modules want to supply an AUTH_SESSION_TICKET, we should
625        leave it up to the admin which module dominiates. */
626
627     retval = pam_get_data(pamh, PS_STATE_ID, (const void **) &old_data);
628     if (retval != PAM_SUCCESS) {
629         D(("no data to base decision on"));
630         return PAM_AUTH_ERR;
631     }
632
633     /*
634      * If ok, export a derived shared secret session ticket to the
635      * client's PAM environment - the ticket has the form
636      *
637      * AUTH_SESSION_TICKET =
638      *        md5[<server_cookie>|<secret_data>|<client_cookie>]
639      *
640      * This is a precursor to supporting a spoof resistant trusted
641      * path mechanism. This shared secret ticket can be used to add
642      * a hard-to-guess checksum to further authentication data.
643      */
644
645     retval = old_data->retval;
646     if (retval == PAM_SUCCESS) {
647         char envticket[sizeof(AUTH_SESSION_TICKET_ENV_FORMAT)+32];
648
649         memcpy(envticket, AUTH_SESSION_TICKET_ENV_FORMAT,
650                sizeof(AUTH_SESSION_TICKET_ENV_FORMAT));
651
652         if (! create_digest(old_data->server_cookie, old_data->secret_data,
653                             old_data->client_cookie,
654                             envticket+sizeof(AUTH_SESSION_TICKET_ENV_FORMAT)-1
655             )) {
656             D(("unable to generate a digest for session ticket"));
657             return PAM_ABORT;
658         }
659
660         D(("putenv[%s]", envticket));
661         retval = pam_putenv(pamh, envticket);
662         memset(envticket, 0, sizeof(envticket));
663     }
664
665     old_data = NULL;
666     D(("done (%d)", retval));
667     
668     return retval;
669 }