4 * PAM authentication management functions for pam_krb5
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 $
9 static const char rcsid[] = "$Id: pam_krb5_auth.c,v 1.18 2000/01/04 08:44:08 fcusack Exp $";
11 #include <sys/types.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 */
22 #include <security/pam_appl.h>
23 #include <security/pam_modules.h>
29 extern krb5_cc_ops krb5_mcc_ops;
31 /* A useful logging macro */
32 #define DLOG(error_func, error_msg) \
34 syslog(LOG_DEBUG, "pam_krb5: pam_sm_authenticate(%s %s): %s: %s", \
35 service, name, error_func, error_msg)
37 /* Authenticate a user via krb5 */
39 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
42 krb5_error_code krbret;
43 krb5_context pam_context;
46 krb5_ccache ccache, ccache_check;
47 krb5_get_init_creds_opt opts;
51 char *princ_name = NULL;
52 char *pass = NULL, *service = NULL;
54 char cache_name[L_tmpnam + 8];
55 char lname[64]; /* local acct name */
58 int debug = 0, try_first_pass = 0, use_first_pass = 0;
59 int forwardable = 0, reuse_ccache = 0, no_ccache = 0;
61 for (i = 0; i < argc; i++) {
62 if (strcmp(argv[i], "debug") == 0)
64 else if (strcmp(argv[i], "try_first_pass") == 0)
66 else if (strcmp(argv[i], "use_first_pass") == 0)
68 else if (strcmp(argv[i], "forwardable") == 0)
70 else if (strcmp(argv[i], "reuse_ccache") == 0)
72 else if (strcmp(argv[i], "no_ccache") == 0)
77 if ((pamret = pam_get_user(pamh, &name, "login: ")) != PAM_SUCCESS) {
78 return PAM_SERVICE_ERR;
81 /* Get service name */
82 (void) pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
88 if ((krbret = krb5_init_context(&pam_context)) != 0) {
89 DLOG("krb5_init_context()", error_message(krbret));
90 return PAM_SERVICE_ERR;
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));
98 krb5_get_init_creds_opt_set_forwardable(&opts, 1);
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;
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;
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;
125 prompt = malloc(16 + strlen(princ_name));
127 DLOG("malloc()", "failure");
128 pamret = PAM_BUF_ERR;
131 (void) sprintf(prompt, "Password for %s: ", princ_name);
133 if (try_first_pass || use_first_pass)
134 (void) pam_get_item(pamh, PAM_AUTHTOK, (const void **) &pass);
139 if ((pamret = get_user_info(pamh, prompt, PAM_PROMPT_ECHO_OFF,
141 DLOG("get_user_info()", pam_strerror(pamh, pamret));
142 pamret = PAM_SERVICE_ERR;
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));
149 pamret = PAM_SERVICE_ERR;
153 /* Now we get it back from the library. */
154 (void) pam_get_item(pamh, PAM_AUTHTOK, (const void **) &pass);
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;
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;
172 if ((pamret = pam_get_item(pamh, PAM_USER, (const void **) &name)
174 DLOG("pam_get_item()", pam_strerror(pamh, pamret));
175 pamret = PAM_SERVICE_ERR;
181 DLOG("getpwnam()", lname);
182 pamret = PAM_USER_UNKNOWN;
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) {
194 pamret = PAM_AUTH_ERR;
198 /* Generate a unique cache_name */
199 strcpy(cache_name, "MEMORY:");
200 (void) tmpnam(&cache_name[7]);
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;
207 if ((krbret = krb5_cc_initialize(pam_context, ccache, princ)) != 0) {
208 DLOG("krb5_cc_initialize()", error_message(krbret));
209 pamret = PAM_SERVICE_ERR;
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;
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;
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;
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;
241 krb5_free_cred_contents(pam_context, &creds);
243 krb5_free_principal(pam_context, princ);
250 krb5_free_context(pam_context);
251 DLOG("exit", pamret ? "failure" : "success");
257 /* redefine this for pam_sm_setcred() */
259 #define DLOG(error_func, error_msg) \
261 syslog(LOG_DEBUG, "pam_krb5: pam_sm_setcred(%s %s): %s: %s", \
262 service, name, error_func, error_msg)
264 /* Called after a successful authentication. Set user credentials. */
266 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
270 krb5_error_code krbret;
271 krb5_context pam_context;
272 krb5_principal princ;
274 krb5_ccache ccache_temp, ccache_perm;
275 krb5_cc_cursor cursor;
278 char *name, *service = NULL;
279 char *cache_name = NULL, *cache_env_name;
280 struct passwd *pw = NULL;
286 if (flags == PAM_REINITIALIZE_CRED)
287 return PAM_SUCCESS; /* XXX Incorrect behavior */
289 if (flags != PAM_ESTABLISH_CRED)
290 return PAM_SERVICE_ERR;
292 for (i = 0; i < argc; i++) {
293 if (strcmp(argv[i], "debug") == 0)
295 else if (strcmp(argv[i], "no_ccache") == 0)
297 else if (strstr(argv[i], "ccache=") == argv[i])
298 cache_name = (char *) &argv[i][7]; /* save for later */
302 if (pam_get_item(pamh, PAM_USER, (const void **) &name)) {
303 return PAM_SERVICE_ERR;
306 /* Get service name */
307 (void) pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
313 if ((krbret = krb5_init_context(&pam_context)) != 0) {
314 DLOG("krb5_init_context()", error_message(krbret));
315 return PAM_SERVICE_ERR;
318 euid = geteuid(); /* Usually 0 */
321 /* Retrieve the cache name */
322 if ((pamret = pam_get_data(pamh, "ccache", (const void **) &ccache_temp))
324 DLOG("pam_get_data()", pam_strerror(pamh, pamret));
325 pamret = PAM_CRED_UNAVAIL;
329 /* Get the uid. This should exist. */
332 DLOG("getpwnam()", name);
333 pamret = PAM_USER_UNKNOWN;
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;
343 if (seteuid(pw->pw_uid)) {
344 DLOG("seteuid()", name);
345 pamret = PAM_SERVICE_ERR;
349 /* Get the cache name */
351 cache_name = malloc(64); /* plenty big */
353 DLOG("malloc()", "failure");
354 pamret = PAM_BUF_ERR;
357 sprintf(cache_name, "FILE:/tmp/krb5cc_%d", pw->pw_uid);
359 /* cache_name was supplied */
360 char *p = calloc(PATH_MAX + 10, 1); /* should be plenty */
361 char *q = cache_name;
363 DLOG("malloc()", "failure");
364 pamret = PAM_BUF_ERR;
369 /* convert %u and %p */
374 sprintf(p, "%d", pw->pw_uid);
376 } else if (*q == 'p') {
377 sprintf(p, "%d", getpid());
380 /* Not a special token */
391 /* Initialize the new ccache */
392 if ((krbret = krb5_cc_get_principal(pam_context, ccache_temp, &princ))
394 DLOG("krb5_cc_get_principal()", error_message(krbret));
395 pamret = PAM_SERVICE_ERR;
398 if ((krbret = krb5_cc_resolve(pam_context, cache_name, &ccache_perm))
400 DLOG("krb5_cc_resolve()", error_message(krbret));
401 pamret = PAM_SERVICE_ERR;
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;
410 /* Prepare for iteration over creds */
411 if ((krbret = krb5_cc_start_seq_get(pam_context, ccache_temp, &cursor))
413 DLOG("krb5_cc_start_seq_get()", error_message(krbret));
414 (void) krb5_cc_destroy(pam_context, ccache_perm);
415 pamret = PAM_SERVICE_ERR;
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,
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;
430 krb5_free_cred_contents(pam_context, &creds);
432 (void) krb5_cc_end_seq_get(pam_context, ccache_temp, &cursor);
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;
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;
448 (void) krb5_cc_close(pam_context, ccache_perm);
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;
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;
467 krb5_free_principal(pam_context, princ);
469 krb5_free_context(pam_context);
470 DLOG("exit", pamret ? "failure" : "success");
471 (void) seteuid(euid);
472 (void) setegid(egid);