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