/* * pam_krb5_auth.c * * PAM authentication management functions for pam_krb5 * * $FreeBSD$ */ static const char rcsid[] = "$Id: pam_krb5_auth.c,v 1.18 2000/01/04 08:44:08 fcusack Exp $"; #include #include #include #include /* PATH_MAX */ #include /* getpwnam */ #include /* tmpnam */ #include /* malloc */ #include /* strchr */ #include /* syslog */ #include /* chown */ #include #include #include #include #include "pam_krb5.h" extern krb5_cc_ops krb5_mcc_ops; /* A useful logging macro */ #define DLOG(error_func, error_msg) \ if (debug) \ syslog(LOG_DEBUG, "pam_krb5: pam_sm_authenticate(%s %s): %s: %s", \ service, name, error_func, error_msg) /* Authenticate a user via krb5 */ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { krb5_error_code krbret; krb5_context pam_context; krb5_creds creds; krb5_principal princ; krb5_ccache ccache, ccache_check; krb5_get_init_creds_opt opts; int pamret, i; const char *name; char *princ_name = NULL; char *pass = NULL, *service = NULL; char *prompt = NULL; char cache_name[L_tmpnam + 8]; char lname[64]; /* local acct name */ struct passwd *pw; int debug = 0, try_first_pass = 0, use_first_pass = 0; int forwardable = 0, reuse_ccache = 0, no_ccache = 0; for (i = 0; i < argc; i++) { if (strcmp(argv[i], "debug") == 0) debug = 1; else if (strcmp(argv[i], "try_first_pass") == 0) try_first_pass = 1; else if (strcmp(argv[i], "use_first_pass") == 0) use_first_pass = 1; else if (strcmp(argv[i], "forwardable") == 0) forwardable = 1; else if (strcmp(argv[i], "reuse_ccache") == 0) reuse_ccache = 1; else if (strcmp(argv[i], "no_ccache") == 0) no_ccache = 1; } /* Get username */ if ((pamret = pam_get_user(pamh, &name, "login: ")) != PAM_SUCCESS) { return PAM_SERVICE_ERR; } /* Get service name */ (void) pam_get_item(pamh, PAM_SERVICE, (const void **) &service); if (!service) service = "unknown"; DLOG("entry", ""); if ((krbret = krb5_init_context(&pam_context)) != 0) { DLOG("krb5_init_context()", error_message(krbret)); return PAM_SERVICE_ERR; } krb5_get_init_creds_opt_init(&opts); memset(&creds, 0, sizeof(krb5_creds)); memset(cache_name, 0, sizeof(cache_name)); memset(lname, 0, sizeof(lname)); if (forwardable) krb5_get_init_creds_opt_set_forwardable(&opts, 1); /* For CNS */ if ((krbret = krb5_cc_register(pam_context, &krb5_mcc_ops, FALSE)) != 0) { /* Solaris dtlogin doesn't call pam_end() on failure */ if (krbret != KRB5_CC_TYPE_EXISTS) { DLOG("krb5_cc_register()", error_message(krbret)); pamret = PAM_SERVICE_ERR; goto cleanup3; } } /* Get principal name */ if ((krbret = krb5_parse_name(pam_context, name, &princ)) != 0) { DLOG("krb5_parse_name()", error_message(krbret)); pamret = PAM_SERVICE_ERR; goto cleanup3; } /* Now convert the principal name into something human readable */ if ((krbret = krb5_unparse_name(pam_context, princ, &princ_name)) != 0) { DLOG("krb5_unparse_name()", error_message(krbret)); pamret = PAM_SERVICE_ERR; goto cleanup2; } /* Get password */ prompt = malloc(16 + strlen(princ_name)); if (!prompt) { DLOG("malloc()", "failure"); pamret = PAM_BUF_ERR; goto cleanup2; } (void) sprintf(prompt, "Password for %s: ", princ_name); if (try_first_pass || use_first_pass) (void) pam_get_item(pamh, PAM_AUTHTOK, (const void **) &pass); get_pass: if (!pass) { try_first_pass = 0; if ((pamret = get_user_info(pamh, prompt, PAM_PROMPT_ECHO_OFF, &pass)) != 0) { DLOG("get_user_info()", pam_strerror(pamh, pamret)); pamret = PAM_SERVICE_ERR; goto cleanup2; } /* We have to free pass. */ if ((pamret = pam_set_item(pamh, PAM_AUTHTOK, pass)) != 0) { DLOG("pam_set_item()", pam_strerror(pamh, pamret)); free(pass); pamret = PAM_SERVICE_ERR; goto cleanup2; } free(pass); /* Now we get it back from the library. */ (void) pam_get_item(pamh, PAM_AUTHTOK, (const void **) &pass); } /* Verify the local user exists (AFTER getting the password) */ if (strchr(name, '@')) { /* get a local account name for this principal */ if ((krbret = krb5_aname_to_localname(pam_context, princ, sizeof(lname), lname)) != 0) { DLOG("krb5_aname_to_localname()", error_message(krbret)); pamret = PAM_USER_UNKNOWN; goto cleanup2; } DLOG("changing PAM_USER to", lname); if ((pamret = pam_set_item(pamh, PAM_USER, lname)) != 0) { DLOG("pam_set_item()", pam_strerror(pamh, pamret)); pamret = PAM_SERVICE_ERR; goto cleanup2; } if ((pamret = pam_get_item(pamh, PAM_USER, (const void **) &name) != 0)) { DLOG("pam_get_item()", pam_strerror(pamh, pamret)); pamret = PAM_SERVICE_ERR; goto cleanup2; } } pw = getpwnam(name); if (!pw) { DLOG("getpwnam()", lname); pamret = PAM_USER_UNKNOWN; goto cleanup2; } /* Get a TGT */ if ((krbret = krb5_get_init_creds_password(pam_context, &creds, princ, pass, pam_prompter, pamh, 0, NULL, &opts)) != 0) { DLOG("krb5_get_init_creds_password()", error_message(krbret)); if (try_first_pass && krbret == KRB5KRB_AP_ERR_BAD_INTEGRITY) { pass = NULL; goto get_pass; } pamret = PAM_AUTH_ERR; goto cleanup2; } /* Generate a unique cache_name */ strcpy(cache_name, "MEMORY:"); (void) tmpnam(&cache_name[7]); if ((krbret = krb5_cc_resolve(pam_context, cache_name, &ccache)) != 0) { DLOG("krb5_cc_resolve()", error_message(krbret)); pamret = PAM_SERVICE_ERR; goto cleanup; } if ((krbret = krb5_cc_initialize(pam_context, ccache, princ)) != 0) { DLOG("krb5_cc_initialize()", error_message(krbret)); pamret = PAM_SERVICE_ERR; goto cleanup; } if ((krbret = krb5_cc_store_cred(pam_context, ccache, &creds)) != 0) { DLOG("krb5_cc_store_cred()", error_message(krbret)); (void) krb5_cc_destroy(pam_context, ccache); pamret = PAM_SERVICE_ERR; goto cleanup; } /* Verify it */ if (verify_krb_v5_tgt(pam_context, ccache, service, debug) == -1) { (void) krb5_cc_destroy(pam_context, ccache); pamret = PAM_AUTH_ERR; goto cleanup; } /* A successful authentication, store ccache for sm_setcred() */ if (!pam_get_data(pamh, "ccache", (const void **) &ccache_check)) { DLOG("pam_get_data()", "ccache data already present"); (void) krb5_cc_destroy(pam_context, ccache); pamret = PAM_AUTH_ERR; goto cleanup; } if ((pamret = pam_set_data(pamh, "ccache", ccache, cleanup_cache)) != 0) { DLOG("pam_set_data()", pam_strerror(pamh, pamret)); (void) krb5_cc_destroy(pam_context, ccache); pamret = PAM_SERVICE_ERR; goto cleanup; } cleanup: krb5_free_cred_contents(pam_context, &creds); cleanup2: krb5_free_principal(pam_context, princ); cleanup3: if (prompt) free(prompt); if (princ_name) free(princ_name); krb5_free_context(pam_context); DLOG("exit", pamret ? "failure" : "success"); return pamret; } /* redefine this for pam_sm_setcred() */ #undef DLOG #define DLOG(error_func, error_msg) \ if (debug) \ syslog(LOG_DEBUG, "pam_krb5: pam_sm_setcred(%s %s): %s: %s", \ service, name, error_func, error_msg) /* Called after a successful authentication. Set user credentials. */ int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { krb5_error_code krbret; krb5_context pam_context; krb5_principal princ; krb5_creds creds; krb5_ccache ccache_temp, ccache_perm; krb5_cc_cursor cursor; int i, pamret; char *name, *service = NULL; char *cache_name = NULL, *cache_env_name; struct passwd *pw = NULL; int debug = 0; uid_t euid; gid_t egid; if (flags == PAM_REINITIALIZE_CRED) return PAM_SUCCESS; /* XXX Incorrect behavior */ if (flags != PAM_ESTABLISH_CRED) return PAM_SERVICE_ERR; for (i = 0; i < argc; i++) { if (strcmp(argv[i], "debug") == 0) debug = 1; else if (strcmp(argv[i], "no_ccache") == 0) return PAM_SUCCESS; else if (strstr(argv[i], "ccache=") == argv[i]) cache_name = (char *) &argv[i][7]; /* save for later */ } /* Get username */ if (pam_get_item(pamh, PAM_USER, (const void **) &name)) { return PAM_SERVICE_ERR; } /* Get service name */ (void) pam_get_item(pamh, PAM_SERVICE, (const void **) &service); if (!service) service = "unknown"; DLOG("entry", ""); if ((krbret = krb5_init_context(&pam_context)) != 0) { DLOG("krb5_init_context()", error_message(krbret)); return PAM_SERVICE_ERR; } euid = geteuid(); /* Usually 0 */ egid = getegid(); /* Retrieve the cache name */ if ((pamret = pam_get_data(pamh, "ccache", (const void **) &ccache_temp)) != 0) { DLOG("pam_get_data()", pam_strerror(pamh, pamret)); pamret = PAM_CRED_UNAVAIL; goto cleanup3; } /* Get the uid. This should exist. */ pw = getpwnam(name); if (!pw) { DLOG("getpwnam()", name); pamret = PAM_USER_UNKNOWN; goto cleanup3; } /* Avoid following a symlink as root */ if (setegid(pw->pw_gid)) { DLOG("setegid()", name); /* XXX should really log group name or id */ pamret = PAM_SERVICE_ERR; goto cleanup3; } if (seteuid(pw->pw_uid)) { DLOG("seteuid()", name); pamret = PAM_SERVICE_ERR; goto cleanup3; } /* Get the cache name */ if (!cache_name) { cache_name = malloc(64); /* plenty big */ if (!cache_name) { DLOG("malloc()", "failure"); pamret = PAM_BUF_ERR; goto cleanup3; } sprintf(cache_name, "FILE:/tmp/krb5cc_%d", pw->pw_uid); } else { /* cache_name was supplied */ char *p = calloc(PATH_MAX + 10, 1); /* should be plenty */ char *q = cache_name; if (!p) { DLOG("malloc()", "failure"); pamret = PAM_BUF_ERR; goto cleanup3; } cache_name = p; /* convert %u and %p */ while (*q) { if (*q == '%') { q++; if (*q == 'u') { sprintf(p, "%d", pw->pw_uid); p += strlen(p); } else if (*q == 'p') { sprintf(p, "%d", getpid()); p += strlen(p); } else { /* Not a special token */ *p++ = '%'; q--; } q++; } else { *p++ = *q++; } } } /* Initialize the new ccache */ if ((krbret = krb5_cc_get_principal(pam_context, ccache_temp, &princ)) != 0) { DLOG("krb5_cc_get_principal()", error_message(krbret)); pamret = PAM_SERVICE_ERR; goto cleanup3; } if ((krbret = krb5_cc_resolve(pam_context, cache_name, &ccache_perm)) != 0) { DLOG("krb5_cc_resolve()", error_message(krbret)); pamret = PAM_SERVICE_ERR; goto cleanup2; } if ((krbret = krb5_cc_initialize(pam_context, ccache_perm, princ)) != 0) { DLOG("krb5_cc_initialize()", error_message(krbret)); pamret = PAM_SERVICE_ERR; goto cleanup2; } /* Prepare for iteration over creds */ if ((krbret = krb5_cc_start_seq_get(pam_context, ccache_temp, &cursor)) != 0) { DLOG("krb5_cc_start_seq_get()", error_message(krbret)); (void) krb5_cc_destroy(pam_context, ccache_perm); pamret = PAM_SERVICE_ERR; goto cleanup2; } /* Copy the creds (should be two of them) */ while ((krbret = krb5_cc_next_cred(pam_context, ccache_temp, &cursor, &creds) == 0)) { if ((krbret = krb5_cc_store_cred(pam_context, ccache_perm, &creds)) != 0) { DLOG("krb5_cc_store_cred()", error_message(krbret)); (void) krb5_cc_destroy(pam_context, ccache_perm); krb5_free_cred_contents(pam_context, &creds); pamret = PAM_SERVICE_ERR; goto cleanup2; } krb5_free_cred_contents(pam_context, &creds); } (void) krb5_cc_end_seq_get(pam_context, ccache_temp, &cursor); if (strstr(cache_name, "FILE:") == cache_name) { if (chown(&cache_name[5], pw->pw_uid, pw->pw_gid) == -1) { DLOG("chown()", strerror(errno)); (void) krb5_cc_destroy(pam_context, ccache_perm); pamret = PAM_SERVICE_ERR; goto cleanup2; } if (chmod(&cache_name[5], (S_IRUSR|S_IWUSR)) == -1) { DLOG("chmod()", strerror(errno)); (void) krb5_cc_destroy(pam_context, ccache_perm); pamret = PAM_SERVICE_ERR; goto cleanup2; } } (void) krb5_cc_close(pam_context, ccache_perm); cache_env_name = malloc(strlen(cache_name) + 12); if (!cache_env_name) { DLOG("malloc()", "failure"); (void) krb5_cc_destroy(pam_context, ccache_perm); pamret = PAM_BUF_ERR; goto cleanup2; } sprintf(cache_env_name, "KRB5CCNAME=%s", cache_name); if ((pamret = pam_putenv(pamh, cache_env_name)) != 0) { DLOG("pam_putenv()", pam_strerror(pamh, pamret)); (void) krb5_cc_destroy(pam_context, ccache_perm); pamret = PAM_SERVICE_ERR; goto cleanup2; } cleanup2: krb5_free_principal(pam_context, princ); cleanup3: krb5_free_context(pam_context); DLOG("exit", pamret ? "failure" : "success"); (void) seteuid(euid); (void) setegid(egid); return pamret; }