Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[dragonfly.git] / lib / libpam / modules / pam_krb5 / pam_krb5_auth.c
1 /*
2  * pam_krb5_auth.c
3  *
4  * PAM authentication management functions for pam_krb5
5  *
6  * $FreeBSD: src/lib/libpam/modules/pam_krb5/pam_krb5_auth.c,v 1.1.2.2 2001/07/29 18:57:30 markm Exp $
7  * $DragonFly: src/lib/libpam/modules/pam_krb5/Attic/pam_krb5_auth.c,v 1.2 2003/06/17 04:26:50 dillon Exp $
8  */
9
10 static const char rcsid[] = "$Id: pam_krb5_auth.c,v 1.18 2000/01/04 08:44:08 fcusack Exp $";
11
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <errno.h>
15 #include <limits.h>     /* PATH_MAX */
16 #include <pwd.h>        /* getpwnam */
17 #include <stdio.h>      /* tmpnam */
18 #include <stdlib.h>     /* malloc  */
19 #include <strings.h>    /* strchr */
20 #include <syslog.h>     /* syslog */
21 #include <unistd.h>     /* chown */
22
23 #include <security/pam_appl.h>
24 #include <security/pam_modules.h>
25
26 #include <krb5.h>
27 #include <com_err.h>
28 #include "pam_krb5.h"
29
30 extern krb5_cc_ops krb5_mcc_ops;
31
32 /* A useful logging macro */
33 #define DLOG(error_func, error_msg) \
34 if (debug) \
35     syslog(LOG_DEBUG, "pam_krb5: pam_sm_authenticate(%s %s): %s: %s", \
36            service, name, error_func, error_msg)
37
38 /* Authenticate a user via krb5 */
39 int
40 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
41                     const char **argv)
42 {
43     krb5_error_code     krbret;
44     krb5_context        pam_context;
45     krb5_creds          creds;
46     krb5_principal      princ;
47     krb5_ccache         ccache, ccache_check;
48     krb5_get_init_creds_opt opts;
49
50     int                 pamret, i;
51     const char          *name;
52     char                *princ_name = NULL;
53     char                *pass = NULL, *service = NULL;
54     char                *prompt = NULL;
55     char                cache_name[L_tmpnam + 8];
56     char                lname[64]; /* local acct name */
57     struct passwd       *pw;
58
59     int debug = 0, try_first_pass = 0, use_first_pass = 0;
60     int forwardable = 0, reuse_ccache = 0, no_ccache = 0;
61
62     for (i = 0; i < argc; i++) {
63         if (strcmp(argv[i], "debug") == 0)
64             debug = 1;
65         else if (strcmp(argv[i], "try_first_pass") == 0)
66             try_first_pass = 1;
67         else if (strcmp(argv[i], "use_first_pass") == 0)
68             use_first_pass = 1;
69         else if (strcmp(argv[i], "forwardable") == 0)
70             forwardable = 1;
71         else if (strcmp(argv[i], "reuse_ccache") == 0)
72             reuse_ccache = 1;
73         else if (strcmp(argv[i], "no_ccache") == 0)
74             no_ccache = 1;
75     }
76
77     /* Get username */
78     if ((pamret = pam_get_user(pamh, &name, "login: ")) != PAM_SUCCESS) {
79         return PAM_SERVICE_ERR;
80     }
81
82     /* Get service name */
83     (void) pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
84     if (!service)
85         service = "unknown";
86
87     DLOG("entry", "");
88
89     if ((krbret = krb5_init_context(&pam_context)) != 0) {
90         DLOG("krb5_init_context()", error_message(krbret));
91         return PAM_SERVICE_ERR;
92     }
93     krb5_get_init_creds_opt_init(&opts);
94     memset(&creds, 0, sizeof(krb5_creds));
95     memset(cache_name, 0, sizeof(cache_name));
96     memset(lname, 0, sizeof(lname));
97
98     if (forwardable)
99         krb5_get_init_creds_opt_set_forwardable(&opts, 1);
100
101     /* For CNS */
102     if ((krbret = krb5_cc_register(pam_context, &krb5_mcc_ops, FALSE)) != 0) {
103         /* Solaris dtlogin doesn't call pam_end() on failure */
104         if (krbret != KRB5_CC_TYPE_EXISTS) {
105             DLOG("krb5_cc_register()", error_message(krbret));
106             pamret = PAM_SERVICE_ERR;
107             goto cleanup3;
108         }
109     }
110
111     /* Get principal name */
112     if ((krbret = krb5_parse_name(pam_context, name, &princ)) != 0) {
113         DLOG("krb5_parse_name()", error_message(krbret));
114         pamret = PAM_SERVICE_ERR;
115         goto cleanup3;
116     }
117
118     /* Now convert the principal name into something human readable */
119     if ((krbret = krb5_unparse_name(pam_context, princ, &princ_name)) != 0) {
120         DLOG("krb5_unparse_name()", error_message(krbret));
121         pamret = PAM_SERVICE_ERR;
122         goto cleanup2;
123     }
124
125     /* Get password */
126     prompt = malloc(16 + strlen(princ_name));
127     if (!prompt) {
128         DLOG("malloc()", "failure");
129         pamret = PAM_BUF_ERR;
130         goto cleanup2;
131     }
132     (void) sprintf(prompt, "Password for %s: ", princ_name);
133
134     if (try_first_pass || use_first_pass)
135         (void) pam_get_item(pamh, PAM_AUTHTOK, (const void **) &pass);
136
137 get_pass:
138     if (!pass) {
139         try_first_pass = 0;
140         if ((pamret = get_user_info(pamh, prompt, PAM_PROMPT_ECHO_OFF,
141           &pass)) != 0) {
142             DLOG("get_user_info()", pam_strerror(pamh, pamret));
143             pamret = PAM_SERVICE_ERR;
144             goto cleanup2;
145         }
146         /* We have to free pass. */
147         if ((pamret = pam_set_item(pamh, PAM_AUTHTOK, pass)) != 0) {
148             DLOG("pam_set_item()", pam_strerror(pamh, pamret));
149             free(pass);
150             pamret = PAM_SERVICE_ERR;
151             goto cleanup2;
152         }
153         free(pass);
154         /* Now we get it back from the library. */
155         (void) pam_get_item(pamh, PAM_AUTHTOK, (const void **) &pass);
156     }
157
158     /* Verify the local user exists (AFTER getting the password) */
159     if (strchr(name, '@')) {
160         /* get a local account name for this principal */
161         if ((krbret = krb5_aname_to_localname(pam_context, princ, 
162           sizeof(lname), lname)) != 0) {
163             DLOG("krb5_aname_to_localname()", error_message(krbret));
164             pamret = PAM_USER_UNKNOWN;
165             goto cleanup2;
166         }
167         DLOG("changing PAM_USER to", lname);
168         if ((pamret = pam_set_item(pamh, PAM_USER, lname)) != 0) {
169             DLOG("pam_set_item()", pam_strerror(pamh, pamret));
170             pamret = PAM_SERVICE_ERR;
171             goto cleanup2;
172         }
173         if ((pamret = pam_get_item(pamh, PAM_USER, (const void **) &name)
174           != 0)) {
175             DLOG("pam_get_item()", pam_strerror(pamh, pamret));
176             pamret = PAM_SERVICE_ERR;
177             goto cleanup2;
178         }
179     }
180     pw = getpwnam(name);
181     if (!pw) {
182         DLOG("getpwnam()", lname);
183         pamret = PAM_USER_UNKNOWN;
184         goto cleanup2;
185     }
186
187     /* Get a TGT */
188     if ((krbret = krb5_get_init_creds_password(pam_context, &creds, princ,
189       pass, pam_prompter, pamh, 0, NULL, &opts)) != 0) {
190         DLOG("krb5_get_init_creds_password()", error_message(krbret));
191         if (try_first_pass && krbret == KRB5KRB_AP_ERR_BAD_INTEGRITY) {
192             pass = NULL;
193             goto get_pass;
194         }
195         pamret = PAM_AUTH_ERR;
196         goto cleanup2;
197     }
198
199     /* Generate a unique cache_name */
200     strcpy(cache_name, "MEMORY:");
201     (void) tmpnam(&cache_name[7]);
202
203     if ((krbret = krb5_cc_resolve(pam_context, cache_name, &ccache)) != 0) {
204         DLOG("krb5_cc_resolve()", error_message(krbret));
205         pamret = PAM_SERVICE_ERR;
206         goto cleanup;
207     }
208     if ((krbret = krb5_cc_initialize(pam_context, ccache, princ)) != 0) {
209         DLOG("krb5_cc_initialize()", error_message(krbret));
210         pamret = PAM_SERVICE_ERR;
211         goto cleanup;
212     }
213     if ((krbret = krb5_cc_store_cred(pam_context, ccache, &creds)) != 0) {
214         DLOG("krb5_cc_store_cred()", error_message(krbret));
215         (void) krb5_cc_destroy(pam_context, ccache);
216         pamret = PAM_SERVICE_ERR;
217         goto cleanup;
218     }
219
220     /* Verify it */
221     if (verify_krb_v5_tgt(pam_context, ccache, service, debug) == -1) {
222         (void) krb5_cc_destroy(pam_context, ccache);
223         pamret = PAM_AUTH_ERR;
224         goto cleanup;
225     }
226
227     /* A successful authentication, store ccache for sm_setcred() */
228     if (!pam_get_data(pamh, "ccache", (const void **) &ccache_check)) {
229         DLOG("pam_get_data()", "ccache data already present");
230         (void) krb5_cc_destroy(pam_context, ccache);
231         pamret = PAM_AUTH_ERR;
232         goto cleanup;
233     }
234     if ((pamret = pam_set_data(pamh, "ccache", ccache, cleanup_cache)) != 0) {
235         DLOG("pam_set_data()", pam_strerror(pamh, pamret));
236         (void) krb5_cc_destroy(pam_context, ccache);
237         pamret = PAM_SERVICE_ERR;
238         goto cleanup;
239     }
240
241 cleanup:
242     krb5_free_cred_contents(pam_context, &creds);
243 cleanup2:
244     krb5_free_principal(pam_context, princ);
245 cleanup3:
246     if (prompt)
247         free(prompt);
248     if (princ_name)
249         free(princ_name);
250
251     krb5_free_context(pam_context);
252     DLOG("exit", pamret ? "failure" : "success");
253     return pamret;
254 }
255
256
257
258 /* redefine this for pam_sm_setcred() */
259 #undef DLOG
260 #define DLOG(error_func, error_msg) \
261 if (debug) \
262     syslog(LOG_DEBUG, "pam_krb5: pam_sm_setcred(%s %s): %s: %s", \
263            service, name, error_func, error_msg)
264
265 /* Called after a successful authentication. Set user credentials. */
266 int
267 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
268                const char **argv)
269 {
270
271     krb5_error_code     krbret;
272     krb5_context        pam_context;
273     krb5_principal      princ;
274     krb5_creds          creds;
275     krb5_ccache         ccache_temp, ccache_perm;
276     krb5_cc_cursor      cursor;
277
278     int                 i, pamret;
279     char                *name, *service = NULL;
280     char                *cache_name = NULL, *cache_env_name;
281     struct passwd       *pw = NULL;
282
283     int         debug = 0;
284     uid_t       euid;
285     gid_t       egid;
286
287     if (flags == PAM_REINITIALIZE_CRED)
288         return PAM_SUCCESS; /* XXX Incorrect behavior */
289
290     if (flags != PAM_ESTABLISH_CRED)
291         return PAM_SERVICE_ERR;
292
293     for (i = 0; i < argc; i++) {
294         if (strcmp(argv[i], "debug") == 0)
295             debug = 1;
296         else if (strcmp(argv[i], "no_ccache") == 0)
297             return PAM_SUCCESS;
298         else if (strstr(argv[i], "ccache=") == argv[i])
299             cache_name = (char *) &argv[i][7]; /* save for later */
300     }
301
302     /* Get username */
303     if (pam_get_item(pamh, PAM_USER, (const void **) &name)) {
304         return PAM_SERVICE_ERR;
305     }
306
307     /* Get service name */
308     (void) pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
309     if (!service)
310         service = "unknown";
311
312     DLOG("entry", "");
313
314     if ((krbret = krb5_init_context(&pam_context)) != 0) {
315         DLOG("krb5_init_context()", error_message(krbret));
316         return PAM_SERVICE_ERR;
317     }
318
319     euid = geteuid(); /* Usually 0 */
320     egid = getegid();
321
322     /* Retrieve the cache name */
323     if ((pamret = pam_get_data(pamh, "ccache", (const void **) &ccache_temp)) 
324       != 0) {
325         DLOG("pam_get_data()", pam_strerror(pamh, pamret));
326         pamret = PAM_CRED_UNAVAIL;
327         goto cleanup3;
328     }
329
330     /* Get the uid. This should exist. */
331     pw = getpwnam(name);
332     if (!pw) {
333         DLOG("getpwnam()", name);
334         pamret = PAM_USER_UNKNOWN;
335         goto cleanup3;
336     }
337
338     /* Avoid following a symlink as root */
339     if (setegid(pw->pw_gid)) {
340         DLOG("setegid()", name); /* XXX should really log group name or id */
341         pamret = PAM_SERVICE_ERR;
342         goto cleanup3;
343     }
344     if (seteuid(pw->pw_uid)) {
345         DLOG("seteuid()", name);
346         pamret = PAM_SERVICE_ERR;
347         goto cleanup3;
348     }
349
350     /* Get the cache name */
351     if (!cache_name) {
352         cache_name = malloc(64); /* plenty big */
353         if (!cache_name) {
354             DLOG("malloc()", "failure");
355             pamret = PAM_BUF_ERR;
356             goto cleanup3;
357         }
358         sprintf(cache_name, "FILE:/tmp/krb5cc_%d", pw->pw_uid);
359     } else {
360         /* cache_name was supplied */
361         char *p = calloc(PATH_MAX + 10, 1); /* should be plenty */
362         char *q = cache_name;
363         if (!p) {
364             DLOG("malloc()", "failure");
365             pamret = PAM_BUF_ERR;
366             goto cleanup3;
367         }
368         cache_name = p;
369         
370         /* convert %u and %p */
371         while (*q) {
372             if (*q == '%') {
373                 q++;
374                 if (*q == 'u') {
375                     sprintf(p, "%d", pw->pw_uid);
376                     p += strlen(p);
377                 } else if (*q == 'p') {
378                     sprintf(p, "%d", getpid());
379                     p += strlen(p);
380                 } else {
381                     /* Not a special token */
382                     *p++ = '%';
383                     q--;
384                 }
385                 q++;
386             } else {
387                 *p++ = *q++;
388             }
389         }
390     }
391
392     /* Initialize the new ccache */
393     if ((krbret = krb5_cc_get_principal(pam_context, ccache_temp, &princ)) 
394       != 0) {
395         DLOG("krb5_cc_get_principal()", error_message(krbret));
396         pamret = PAM_SERVICE_ERR;
397         goto cleanup3;
398     }
399     if ((krbret = krb5_cc_resolve(pam_context, cache_name, &ccache_perm)) 
400       != 0) {
401         DLOG("krb5_cc_resolve()", error_message(krbret));
402         pamret = PAM_SERVICE_ERR;
403         goto cleanup2;
404     }
405     if ((krbret = krb5_cc_initialize(pam_context, ccache_perm, princ)) != 0) {
406         DLOG("krb5_cc_initialize()", error_message(krbret));
407         pamret = PAM_SERVICE_ERR;
408         goto cleanup2;
409     }
410
411     /* Prepare for iteration over creds */
412     if ((krbret = krb5_cc_start_seq_get(pam_context, ccache_temp, &cursor)) 
413       != 0) {
414         DLOG("krb5_cc_start_seq_get()", error_message(krbret));
415         (void) krb5_cc_destroy(pam_context, ccache_perm);
416         pamret = PAM_SERVICE_ERR;
417         goto cleanup2;
418     }
419
420     /* Copy the creds (should be two of them) */
421     while ((krbret = krb5_cc_next_cred(pam_context, ccache_temp,
422         &cursor, &creds) == 0)) {
423             if ((krbret = krb5_cc_store_cred(pam_context, ccache_perm, 
424                 &creds)) != 0) {
425             DLOG("krb5_cc_store_cred()", error_message(krbret));
426             (void) krb5_cc_destroy(pam_context, ccache_perm);
427             krb5_free_cred_contents(pam_context, &creds);
428             pamret = PAM_SERVICE_ERR;
429             goto cleanup2;
430         }
431         krb5_free_cred_contents(pam_context, &creds);
432     }
433     (void) krb5_cc_end_seq_get(pam_context, ccache_temp, &cursor);
434
435     if (strstr(cache_name, "FILE:") == cache_name) {
436         if (chown(&cache_name[5], pw->pw_uid, pw->pw_gid) == -1) {
437             DLOG("chown()", strerror(errno));
438             (void) krb5_cc_destroy(pam_context, ccache_perm);
439             pamret = PAM_SERVICE_ERR;   
440             goto cleanup2;
441         }
442         if (chmod(&cache_name[5], (S_IRUSR|S_IWUSR)) == -1) {
443             DLOG("chmod()", strerror(errno));
444             (void) krb5_cc_destroy(pam_context, ccache_perm);
445             pamret = PAM_SERVICE_ERR;
446             goto cleanup2;
447         }
448     }
449     (void) krb5_cc_close(pam_context, ccache_perm);
450
451     cache_env_name = malloc(strlen(cache_name) + 12);
452     if (!cache_env_name) {
453         DLOG("malloc()", "failure");
454         (void) krb5_cc_destroy(pam_context, ccache_perm);
455         pamret = PAM_BUF_ERR;
456         goto cleanup2;
457     }
458
459     sprintf(cache_env_name, "KRB5CCNAME=%s", cache_name);
460     if ((pamret = pam_putenv(pamh, cache_env_name)) != 0) {
461         DLOG("pam_putenv()", pam_strerror(pamh, pamret));
462         (void) krb5_cc_destroy(pam_context, ccache_perm);
463         pamret = PAM_SERVICE_ERR;
464         goto cleanup2;
465     }
466
467 cleanup2:
468     krb5_free_principal(pam_context, princ);
469 cleanup3:
470     krb5_free_context(pam_context);
471     DLOG("exit", pamret ? "failure" : "success");
472     (void) seteuid(euid);
473     (void) setegid(egid);
474     return pamret;
475 }
476