/** Create and return a new connection * * libkrb5(s) can talk to the KDC over TCP. Were assuming something sane is implemented * by libkrb5 and that it does connection caching associated with contexts, so it's * worth using a connection pool to preserve connections when workers die. * * @param instance rlm_krb5 instance instance. * @return A new context or NULL on error. */ void *mod_conn_create(void *instance) { rlm_krb5_t *inst = instance; rlm_krb5_handle_t *conn; krb5_error_code ret; MEM(conn = talloc_zero(instance, rlm_krb5_handle_t)); ret = krb5_init_context(&conn->context); if (ret) { ERROR("rlm_krb5 (%s): Context initialisation failed: %s", inst->xlat_name, rlm_krb5_error(NULL, ret)); return NULL; } talloc_set_destructor(conn, _free_handle); ret = inst->keytabname ? krb5_kt_resolve(conn->context, inst->keytabname, &conn->keytab) : krb5_kt_default(conn->context, &conn->keytab); if (ret) { ERROR("Resolving keytab failed: %s", rlm_krb5_error(conn->context, ret)); goto cleanup; } #ifdef HEIMDAL_KRB5 ret = krb5_cc_new_unique(conn->context, "MEMORY", NULL, &conn->ccache); if (ret) { ERROR("rlm_krb5 (%s): Credential cache creation failed: %s", inst->xlat_name, rlm_krb5_error(conn->context, ret)); return NULL; } krb5_verify_opt_init(&conn->options); krb5_verify_opt_set_ccache(&conn->options, conn->ccache); krb5_verify_opt_set_keytab(&conn->options, conn->keytab); krb5_verify_opt_set_secure(&conn->options, true); if (inst->service) { krb5_verify_opt_set_service(&conn->options, inst->service); } #else krb5_verify_init_creds_opt_set_ap_req_nofail(inst->vic_options, true); #endif return conn; cleanup: talloc_free(conn); return NULL; }
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL krb5_verify_user(krb5_context context, krb5_principal principal, krb5_ccache ccache, const char *password, krb5_boolean secure, const char *service) { krb5_verify_opt opt; krb5_verify_opt_init(&opt); krb5_verify_opt_set_ccache(&opt, ccache); krb5_verify_opt_set_secure(&opt, secure); krb5_verify_opt_set_service(&opt, service); return krb5_verify_user_opt(context, principal, password, &opt); }
char * /* R: allocated response string */ auth_krb5 ( /* PARAMETERS */ const char *user, /* I: plaintext authenticator */ const char *password, /* I: plaintext password */ const char *service, /* I: service authenticating to */ const char *realm /* I: user's realm */ /* END PARAMETERS */ ) { /* VARIABLES */ krb5_context context; krb5_ccache ccache = NULL; krb5_keytab kt = NULL; krb5_principal auth_user; krb5_verify_opt opt; char * result; char tfname[2048]; char principalbuf[2048]; /* END VARIABLES */ if (!user || !password) { syslog(LOG_ERR, "auth_krb5: NULL password or username?"); return strdup("NO saslauthd internal NULL password or username"); } if (krb5_init_context(&context)) { syslog(LOG_ERR, "auth_krb5: krb5_init_context"); return strdup("NO saslauthd internal krb5_init_context error"); } if (form_principal_name(user, service, realm, principalbuf, sizeof (principalbuf))) { syslog(LOG_ERR, "auth_krb5: form_principal_name"); return strdup("NO saslauthd principal name error"); } if (krb5_parse_name (context, principalbuf, &auth_user)) { krb5_free_context(context); syslog(LOG_ERR, "auth_krb5: krb5_parse_name"); return strdup("NO saslauthd internal krb5_parse_name error"); } if (krbtf_name(tfname, sizeof (tfname)) != 0) { syslog(LOG_ERR, "auth_krb5: could not generate ccache name"); return strdup("NO saslauthd internal error"); } if (krb5_cc_resolve(context, tfname, &ccache)) { krb5_free_principal(context, auth_user); krb5_free_context(context); syslog(LOG_ERR, "auth_krb5: krb5_cc_resolve"); return strdup("NO saslauthd internal error"); } if (keytabname) { if (krb5_kt_resolve(context, keytabname, &kt)) { krb5_free_principal(context, auth_user); krb5_cc_destroy(context, ccache); krb5_free_context(context); syslog(LOG_ERR, "auth_krb5: krb5_kt_resolve"); return strdup("NO saslauthd internal error"); } } krb5_verify_opt_init(&opt); krb5_verify_opt_set_secure(&opt, 1); krb5_verify_opt_set_ccache(&opt, ccache); if (kt) krb5_verify_opt_set_keytab(&opt, kt); krb5_verify_opt_set_service(&opt, verify_principal); if (krb5_verify_user_opt(context, auth_user, password, &opt)) { result = strdup("NO krb5_verify_user_opt failed"); } else { result = strdup("OK"); } krb5_free_principal(context, auth_user); krb5_cc_destroy(context, ccache); if (kt) krb5_kt_close(context, kt); krb5_free_context(context); return result; }
/* * validate user/pass (Heimdal) */ static int krb5_auth(void *instance, REQUEST *request) { rlm_krb5_t *inst = instance; krb5_error_code ret; krb5_ccache id; krb5_principal userP; krb5_context context = *(inst->context); /* copy data */ const char *user, *pass; char service[SERVICE_NAME_LEN] = "host"; char *server_name = NULL; char *princ_name; krb5_verify_opt krb_verify_options; krb5_keytab keytab; if (inst->service_princ != NULL) { server_name = strchr(inst->service_princ, '/'); if (server_name != NULL) { *server_name = '\0'; } strlcpy(service, inst->service_princ, sizeof(service)); if (server_name != NULL) { *server_name = '/'; server_name++; } } /* * We can only authenticate user requests which HAVE * a User-Name attribute. */ if (!request->username) { radlog(L_AUTH, "rlm_krb5: Attribute \"User-Name\" is required for authentication."); return RLM_MODULE_INVALID; } /* * We can only authenticate user requests which HAVE * a User-Password attribute. */ if (!request->password) { radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication."); return RLM_MODULE_INVALID; } /* * Ensure that we're being passed a plain-text password, * and not anything else. */ if (request->password->attribute != PW_USER_PASSWORD) { radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->name); return RLM_MODULE_INVALID; } user = request->username->vp_strvalue; pass = request->password->vp_strvalue; ret = krb5_parse_name(context, user, &userP); if (ret) { radlog(L_AUTH, "rlm_krb5: [%s] krb5_parse_name failed: %s", user, error_message(ret)); return RLM_MODULE_REJECT; } /* * Heimdal krb5 verification. */ /* * The following bit allows us to also log user/instance@REALM if someone * logs in using an instance. */ ret = krb5_unparse_name(context, userP, &princ_name); if (ret != 0) { radlog(L_AUTH, "rlm_krb5: Unparsable name"); } else { radlog(L_AUTH, "rlm_krb5: Parsed name is: %s", princ_name); free(princ_name); } krb5_cc_default(context, &id); /* * Set up krb5_verify_user options. */ krb5_verify_opt_init(&krb_verify_options); krb5_verify_opt_set_ccache(&krb_verify_options, id); /* * Resolve keytab name. This allows us to use something other than * the default system keytab */ if (inst->keytab != NULL) { ret = krb5_kt_resolve(context, inst->keytab, &keytab); if (ret) { radlog(L_AUTH, "rlm_krb5: unable to resolve keytab %s: %s", inst->keytab, error_message(ret)); krb5_kt_close(context, keytab); return RLM_MODULE_REJECT; } krb5_verify_opt_set_keytab(&krb_verify_options, keytab); } /* * Verify aquired credentials against the keytab. */ krb5_verify_opt_set_secure(&krb_verify_options, 1); /* * Allow us to use an arbitrary service name. */ krb5_verify_opt_set_service(&krb_verify_options, service); /* * Verify the user, using the above set options. */ ret = krb5_verify_user_opt(context, userP, pass, &krb_verify_options); /* * We are done with the keytab, close it. */ krb5_kt_close(context, keytab); if (ret == 0) return RLM_MODULE_OK; radlog(L_AUTH, "rlm_krb5: failed verify_user: %s (%s@%s)", error_message(ret), *userP->name.name_string.val, userP->realm); return RLM_MODULE_REJECT; }
/* * Validate user/pass (Heimdal) */ static rlm_rcode_t krb5_auth(void *instance, REQUEST *request) { rlm_krb5_t *inst = instance; rlm_rcode_t rcode; krb5_error_code ret; krb5_principal client; krb5_ccache ccache; krb5_keytab keytab; krb5_verify_opt options; krb5_context *context = NULL; /* * See above in MIT krb5_auth */ ret = krb5_copy_context(*(inst->context), context); if (ret) { radlog(L_ERR, "rlm_krb5 (%s): Error cloning krb5 context: %s", inst->xlat_name, error_message(ret)); return RLM_MODULE_FAIL; } /* * Setup krb5_verify_user options * * Not entirely sure this is necessary, but as we use context * to get the cache handle, we probably do have to do this with * the cloned context. */ krb5_cc_default(*context, &ccache); krb5_verify_opt_init(&options); krb5_verify_opt_set_ccache(&options, ccache); memset(&keytab, 0, sizeof(keytab)); ret = inst->keytabname ? krb5_kt_resolve(*context, inst->keytabname, &keytab) : krb5_kt_default(*context, &keytab); if (ret) { radlog(L_ERR, "rlm_krb5 (%s): Resolving keytab failed: %s", inst->xlat_name, error_message(ret)); goto cleanup; } krb5_verify_opt_set_keytab(&options, keytab); krb5_verify_opt_set_secure(&options, TRUE); if (inst->service) { krb5_verify_opt_set_service(&options, inst->service); } rcode = krb5_parse_user(inst, request, &client); if (rcode != RLM_MODULE_OK) goto cleanup; /* * Verify the user, using the options we set in instantiate */ ret = krb5_verify_user_opt(*context, client, request->password->vp_strvalue, &options); if (ret) { switch (ret) { case KRB5_LIBOS_BADPWDMATCH: case KRB5KRB_AP_ERR_BAD_INTEGRITY: RDEBUG("Provided password was incorrect: %s", error_message(ret)); rcode = RLM_MODULE_REJECT; break; case KRB5KDC_ERR_KEY_EXP: case KRB5KDC_ERR_CLIENT_REVOKED: case KRB5KDC_ERR_SERVICE_REVOKED: RDEBUG("Account has been locked out: %s", error_message(ret)); rcode = RLM_MODULE_USERLOCK; break; case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN: RDEBUG("User not found: %s", error_message(ret)); rcode = RLM_MODULE_NOTFOUND; default: radlog(L_ERR, "rlm_krb5 (%s): Verifying user failed: " "%s", inst->xlat_name, error_message(ret)); rcode = RLM_MODULE_FAIL; break; } goto cleanup; } cleanup: krb5_free_context(*context); krb5_kt_close(*context, keytab); return rcode; }