Merge from vendor branch BINUTILS:
[dragonfly.git] / contrib / libpam / libpamc / test / modules / pam_secret.c
1 /*
2  * $Id: pam_secret.c,v 1.2 2001/01/20 22:29:47 agmorgan Exp $
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 /* #define DEBUG */
13
14 #include <fcntl.h>
15 #include <pwd.h>
16 #include <stdio.h>
17 #include <string.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20
21 #include <security/pam_modules.h>
22 #include <security/pam_client.h>
23 #include <security/_pam_macros.h>
24
25 /*
26  * This is a sample module that demonstrates the use of binary prompts
27  * and how they can be used to implement sophisticated authentication
28  * schemes.
29  */
30
31 struct ps_state_s {
32     int retval;        /* last retval returned by the authentication fn */
33     int state;         /* what state the module was in when it
34                           returned incomplete */
35
36     char *username;    /* the name of the local user */
37
38     char server_cookie[33]; /* storage for 32 bytes of server cookie */
39     char client_cookie[33]; /* storage for 32 bytes of client cookie */
40
41     char *secret_data; /* pointer to <NUL> terminated secret_data */
42     int invalid_secret;  /* indication of whether the secret is valid */
43
44     pamc_bp_t current_prompt;    /* place to store the current prompt */
45     pamc_bp_t current_reply;     /* place to receive the reply prompt */
46 };
47
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
54
55 #define MAX_LEN_HOSTNAME       512
56 #define MAX_FILE_LINE_LEN      1024
57
58 /*
59  * Routine for generating 16*8 bits of random data represented in ASCII hex
60  */
61
62 static int generate_cookie(unsigned char *buffer_33)
63 {
64     static const char hexarray[] = "0123456789abcdef";
65     int i, fd;
66
67     /* fill buffer_33 with 32 hex characters (lower case) + '\0' */
68     fd = open("/dev/urandom", O_RDONLY);
69     if (fd < 0) {
70         D(("failed to open /dev/urandom"));
71         return 0;
72     }
73     read(fd, buffer_33 + 16, 16);
74     close(fd);
75
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)];
80     }
81
82     buffer_33[32] = '\0';
83
84     return 1;
85 }
86
87 /*
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
95  * a whole...]
96  */
97
98 #define COMMAND_FORMAT "/bin/echo -n '%s|%s|%s'|/usr/bin/md5sum -"
99
100 int create_digest(const char *d1, const char *d2, const char *d3,
101                   char *buffer_33)
102 {
103     int length;
104     char *buffer;
105     FILE *pipe;
106
107     length = strlen(d1)+strlen(d2)+strlen(d3)+sizeof(COMMAND_FORMAT);
108     buffer = malloc(length);
109     if (buffer == NULL) {
110         D(("out of memory"));
111         return 0;
112     }
113
114     sprintf(buffer, COMMAND_FORMAT, d1,d2,d3);
115
116     D(("executing pipe [%s]", buffer));
117     pipe = popen(buffer, "r");
118     memset(buffer, 0, length);
119     free(buffer);
120
121     if (pipe == NULL) {
122         D(("failed to launch pipe"));
123         return 0;
124     }
125
126     if (fgets(buffer_33, 33, pipe) == NULL) {
127         D(("failed to read digest"));
128         return 0;
129     }
130
131     if (strlen(buffer_33) != 32) {
132         D(("digest was not 32 chars"));
133         return 0;
134     }
135
136     fclose(pipe);
137
138     D(("done [%s]", buffer_33));
139
140     return 1;
141 }
142
143 /*
144  * method to attempt to instruct the application's conversation function
145  */
146
147 static int converse(pam_handle_t *pamh, struct ps_state_s *new)
148 {
149     int retval;
150     struct pam_conv *conv;
151
152     D(("called"));
153
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;
159
160         memset(&msg, 0, sizeof(msg));
161         msg.msg_style = PAM_BINARY_PROMPT;
162         msg.msg = (const char *) new->current_prompt;
163         msg_ptr = &msg;
164
165         single_reply = NULL;
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;
170             } else {
171                 new->current_reply = (pamc_bp_t) single_reply->resp;
172                 single_reply->resp = NULL;
173             }
174         }
175
176         if (single_reply) {
177             free(single_reply);
178         }
179     }
180
181 #ifdef DEBUG
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)));
186     }
187     D(("returning %s", pam_strerror(pamh, retval)));
188 #endif
189
190     return retval;
191 }
192
193 /*
194  * identify the secret in question
195  */
196
197 #define SECRET_FILE_FORMAT "%s/.secret@here"
198
199 char *identify_secret(char *identity, const char *user)
200 {
201     struct passwd *pwd;
202     char *temp;
203     FILE *secrets;
204     int length_id;
205
206     pwd = getpwnam(user);
207     if ((pwd == NULL) || (pwd->pw_dir == NULL)) {
208         D(("user [%s] is not known", user));
209     }
210
211     length_id = strlen(pwd->pw_dir) + sizeof(SECRET_FILE_FORMAT);
212     temp = malloc(length_id);
213     if (temp == NULL) {
214         D(("out of memory"));
215         pwd = NULL;
216         return NULL;
217     }
218
219     sprintf(temp, SECRET_FILE_FORMAT, pwd->pw_dir);
220     pwd = NULL;
221
222     D(("opening key file [%s]", temp));
223     secrets = fopen(temp, "r");
224     memset(temp, 0, length_id);
225
226     if (secrets == NULL) {
227         D(("failed to open key file"));
228         return NULL;
229     }
230
231     length_id = strlen(identity);
232     temp = malloc(MAX_FILE_LINE_LEN);
233
234     for (;;) {
235         char *secret = NULL;
236
237         if (fgets(temp, MAX_FILE_LINE_LEN, secrets) == NULL) {
238             fclose(secrets);
239             return NULL;
240         }
241
242         D(("cf[%s][%s]", identity, temp));
243         if (memcmp(temp, identity, length_id)) {
244             continue;
245         }
246
247         D(("found entry"));
248         fclose(secrets);
249
250         for (secret=temp+length_id; *secret; ++secret) {
251             if (!(*secret == ' ' || *secret == '\n' || *secret == '\t')) {
252                 break;
253             }
254         }
255
256         memmove(temp, secret, MAX_FILE_LINE_LEN-(secret-(temp+length_id)));
257         secret = temp;
258
259         for (; *secret; ++secret) {
260             if (*secret == ' ' || *secret == '\n' || *secret == '\t') {
261                 break;
262             }
263         }
264
265         if (*secret) {
266             *secret = '\0';
267         }
268
269         D(("secret found [%s]", temp));
270
271         return temp;
272     }
273
274     /* NOT REACHED */
275 }
276
277 /*
278  * function to perform the two message authentication process
279  * (with support for event driven conversation functions)
280  */
281
282 static int auth_sequence(pam_handle_t *pamh,
283                          const struct ps_state_s *old, struct ps_state_s *new)
284 {
285     const char *rhostname;
286     const char *rusername;
287     int retval;
288
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;
293         return PAM_AUTH_ERR;
294     }
295
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;
300         return PAM_AUTH_ERR;
301     }
302
303     D(("switch on new->state=%d [%s@%s]", new->state, rusername, rhostname));
304     switch (new->state) {
305
306     case PS_STATE_INIT:
307     {
308         const char *user = NULL;
309
310         retval = pam_get_user(pamh, &user, NULL);
311
312         if ((retval == PAM_SUCCESS) && (user == NULL)) {
313             D(("success but no username?"));
314             new->state = PS_STATE_DEAD;
315             retval = PAM_USER_UNKNOWN;
316         }
317
318         if (retval != PAM_SUCCESS) {
319             if (retval == PAM_CONV_AGAIN) {
320                 retval = PAM_INCOMPLETE;
321             } else {
322                 new->state = PS_STATE_DEAD;
323             }
324             D(("state init failed: %s", pam_strerror(pamh, retval)));
325             return retval;
326         }
327
328         /* nothing else in this 'case' can be retried */
329
330         new->username = strdup(user);
331         if (new->username == NULL) {
332             D(("out of memory"));
333             new->state = PS_STATE_DEAD;
334             return PAM_BUF_ERR;
335         }
336
337         if (! generate_cookie(new->server_cookie)) {
338             D(("problem generating server cookie"));
339             new->state = PS_STATE_DEAD;
340             return PAM_ABORT;
341         }
342
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,
349                 new->server_cookie);
350
351         /* note, the BP is guaranteed by the spec to be <NUL> terminated */
352         D(("initialization packet [%s]", PAM_BP_DATA(new->current_prompt)));
353
354         /* fall through */
355         new->state = PS_STATE_PROMPT1;
356
357         D(("fall through to state_prompt1"));
358     }
359
360     case PS_STATE_PROMPT1:
361     {
362         int i, length;
363
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;
370             } else {
371                 new->state = PS_STATE_DEAD;
372                 return retval;
373             }
374         }
375
376         if (retval != PAM_SUCCESS) {
377             D(("failed to read ruser@rhost"));
378             new->state = PS_STATE_DEAD;
379             return PAM_AUTH_ERR;
380         }
381
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;
387             return PAM_CONV_ERR;
388         }
389
390         /* find | */
391         length = PAM_BP_LENGTH(new->current_reply);
392         for (i=0; i<length; ++i) {
393             if (PAM_BP_RDATA(new->current_reply)[i] == '|') {
394                 break;
395             }
396         }
397         if (i >= length) {
398             D(("malformed response (no |) of length %d", length));
399             new->state = PS_STATE_DEAD;
400             return PAM_CONV_ERR;
401         }
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;
406             return PAM_CONV_ERR;
407         }
408
409         /* copy client cookie */
410         memcpy(new->client_cookie, PAM_BP_RDATA(new->current_reply)+i, 32);
411
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);
418
419         /* look up the secret */
420         new->invalid_secret = 0;
421
422         if (new->secret_data == NULL) {
423             char *ruser_rhost;
424
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;
429                 return PAM_BUF_ERR;
430             }
431             sprintf(ruser_rhost, "%s@%s", rusername, rhostname);
432             new->secret_data = identify_secret(ruser_rhost, new->username);
433
434             memset(ruser_rhost, 0, strlen(ruser_rhost));
435             free(ruser_rhost);
436         }
437
438         if (new->secret_data == NULL) {
439             D(("secret not found for user"));
440             new->invalid_secret = 1;
441
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;
447                 return PAM_BUF_ERR;
448             }
449             if (! generate_cookie(new->secret_data)) {
450                 D(("what's up - no fake cookie generated?"));
451                 new->state = PS_STATE_DEAD;
452                 return PAM_ABORT;
453             }
454         }
455
456         /* construct md5[<client_cookie>|<server_cookie>|<secret_data>] */
457         if (! create_digest(new->client_cookie, new->server_cookie,
458                             new->secret_data,
459                             PAM_BP_WDATA(new->current_prompt)+i)) {
460             D(("md5 digesting failed"));
461             new->state = PS_STATE_DEAD;
462             return PAM_ABORT;
463         }
464
465         /* prompt2 is now constructed - fall through to send it */
466     }
467
468     case PS_STATE_PROMPT2:
469     {
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;
476             } else {
477                 new->state = PS_STATE_DEAD;
478                 return retval;
479             }
480         }
481
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. */
485
486         new->state = PS_STATE_DEAD;
487
488         /* expect reply:{md5[<secret_data>|<server_cookie>|<client_cookie>]} */
489
490         {
491             int cf;
492             char expectation[33];
493
494             if (!create_digest(new->secret_data, new->server_cookie,
495                                new->client_cookie, expectation)) {
496                 new->state = PS_STATE_DEAD;
497                 return PAM_ABORT;
498             }
499
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"));
504                 return PAM_AUTH_ERR;
505             }
506         }
507
508         D(("correctly authenticated :)"));
509         return PAM_SUCCESS;
510     }
511
512     default:
513         new->state = PS_STATE_DEAD;
514
515     case PS_STATE_DEAD:
516
517         D(("state is currently dead/unknown"));
518         return PAM_AUTH_ERR;
519     }
520
521     fprintf(stderr, "pam_secret: this should not be reached\n");
522     return PAM_ABORT;
523 }
524
525 static void clean_data(pam_handle_t *pamh, void *datum, int error_status)
526 {
527     struct ps_state_s *data = datum;
528
529     D(("liberating datum=%p", datum));
530
531     if (data) {
532         D(("renew prompt"));
533         PAM_BP_RENEW(&data->current_prompt, 0, 0);
534         D(("renew reply"));
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"));
539         free(data);
540     }
541
542     D(("done."));
543 }
544
545 /*
546  * front end for the authentication function
547  */
548
549 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
550                         int argc, const char **argv)
551 {
552     int retval;
553     struct ps_state_s *new_data;
554     const struct ps_state_s *old_data;
555
556     D(("called"));
557
558     new_data = calloc(1, sizeof(struct ps_state_s));
559     if (new_data == NULL) {
560         D(("out of memory"));
561         return PAM_BUF_ERR;
562     }
563     new_data->retval = PAM_SUCCESS;
564
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);
572         }
573         if (old_data->secret_data) {
574             new_data->secret_data = strdup(old_data->secret_data);
575         }
576         if (old_data->current_prompt) {
577             int length;
578
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));
584         }
585         /* don't need to duplicate current_reply */
586     } else {
587         old_data = NULL;
588         new_data->state = PS_STATE_INIT;
589     }
590
591     D(("call auth_sequence"));
592     new_data->retval = auth_sequence(pamh, old_data, new_data);
593     D(("returned from auth_sequence"));
594
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"));
598     } else {
599         retval = new_data->retval;
600     }
601
602     old_data = new_data = NULL;
603
604     D(("done (%d)", retval));
605     return retval;
606 }
607
608 /*
609  * front end for the credential setting function
610  */
611
612 #define AUTH_SESSION_TICKET_ENV_FORMAT "AUTH_SESSION_TICKET="
613
614 int pam_sm_setcred(pam_handle_t *pamh, int flags,
615                    int argc, const char **argv)
616 {
617     int retval;
618     const struct ps_state_s *old_data;
619
620     D(("called"));
621
622     /* XXX - need to pay attention to the various flavors of call */
623
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. */
627
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"));
631         return PAM_AUTH_ERR;
632     }
633
634     /*
635      * If ok, export a derived shared secret session ticket to the
636      * client's PAM environment - the ticket has the form
637      *
638      * AUTH_SESSION_TICKET =
639      *        md5[<server_cookie>|<secret_data>|<client_cookie>]
640      *
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.
644      */
645
646     retval = old_data->retval;
647     if (retval == PAM_SUCCESS) {
648         char envticket[sizeof(AUTH_SESSION_TICKET_ENV_FORMAT)+32];
649
650         memcpy(envticket, AUTH_SESSION_TICKET_ENV_FORMAT,
651                sizeof(AUTH_SESSION_TICKET_ENV_FORMAT));
652
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
656             )) {
657             D(("unable to generate a digest for session ticket"));
658             return PAM_ABORT;
659         }
660
661         D(("putenv[%s]", envticket));
662         retval = pam_putenv(pamh, envticket);
663         memset(envticket, 0, sizeof(envticket));
664     }
665
666     old_data = NULL;
667     D(("done (%d)", retval));
668     
669     return retval;
670 }