/* * kpass.c * A function to do Kerberos password verification. * */ #include #include #include #include #include #include #include #include #include #define CALL_FROM_PERL #define HAVE_VSNPRINTF #define TKT_LIFETIME 30 /* 30 seconds */ #define USE_MEMORY_CC /* use in-memory ccache */ /* * com_err hook for logging errors to syslog. * * Portability note: We don't currently happen to use the va_list args in any * of our calls to com_err, so it's currently fine to skip the buffer * formatting on systems that don't have that vsnprintf(3). */ static void syslog_err(const char *tag, long code, const char *format, va_list args) { #ifdef HAVE_VSNPRINTF char buffer[128]; (void)vsnprintf(buffer, sizeof(buffer), format, args); syslog(LOG_ERR, "%s: %s %s", tag, error_message(code), buffer); #else syslog(LOG_ERR, "%s: %s %s", tag, error_message(code), format); #endif return; } /* * Check a Kerberos function's return value; if any error occurred, log an * explanatory message and bail out. */ #define FAIL(err, tag) \ if (err) { \ void (*proc)(const char *, long, const char *, va_list); \ proc = set_com_err_hook(syslog_err); \ com_err("kpass", (err), (tag)); \ (void)set_com_err_hook(proc); \ goto cleanup; \ } /* * Kerberos password verification. Attempt to obtain short term * credentials for a given username and password from Kerberos AS, * then obtain credentials for a local service from Kerberos TGS * to verify the authenticity of the AS response. * * Arguments: * username, password The username and password to verify. * service, host A local service whose key will be used to * verify the authenticity of the Kerberos * credentials. Specifying host as NULL * says use my primary hostname. * kt_pathname Path of keytab file, or NULL to use default. * (Typical form "FILE:/abs/path/name".) * * Return -1 if an error occurs, 0 if the username or password is incorrect, * or 1 if password verification is successful. */ int kpass(username, password, service, host, kt_pathname) char *username; char *password; char *service; char *host; char *kt_pathname; { krb5_error_code err; krb5_context context; krb5_auth_context auth_context = NULL; krb5_creds credentials; krb5_principal user_principal, service_principal; krb5_keytab keytab = NULL; krb5_ccache ccache; char ccache_name[L_tmpnam + 8]; krb5_get_init_creds_opt gic_options; #ifndef NO_REPLAYCACHE krb5_verify_init_creds_opt vic_options; #endif krb5_data apreq_pkt; char myhostname[256], sprinc[256]; int have_user_principal = 0, have_service_principal = 0, have_keytab = 0, have_credentials = 0, success = -1; apreq_pkt.data = NULL; err = krb5_init_context(&context); FAIL(err, "from krb5_init_context"); #ifdef NEED_INIT_ETS krb5_init_ets(context); #endif #ifdef USE_MEMORY_CC (void) memset(ccache_name, 0, sizeof(ccache_name)); (void) strcpy(ccache_name, "MEMORY:"); (void) tmpnam(&ccache_name[7]); err = krb5_cc_resolve(context, ccache_name, &ccache); FAIL(err, "from krb5_cc_resolve"); #else err = krb5_cc_default(context, &ccache); FAIL(err, "from cc_default"); #endif #ifdef CALL_FROM_PERL /* * for perl module interface -- i don't know how to pass a C * NULL from perl (is there a way?), so translate the empty string. */ if ( host && (!strcmp(host, "")) ) host = NULL; #endif /* CALL_FROM_PERL */ err = krb5_parse_name(context, username, &user_principal); FAIL(err, "from krb_parse_name"); have_user_principal = 1; err = krb5_cc_initialize(context, ccache, user_principal); FAIL(err, "from krb_cc_initialize"); (void) memset( (char *)&credentials, 0, sizeof(credentials) ); if (!host) { (void) gethostname(myhostname, sizeof(myhostname)); snprintf(sprinc, sizeof(sprinc), "%s/%s", service, myhostname); } else snprintf(sprinc, sizeof(sprinc), "%s/%s", service, host); krb5_get_init_creds_opt_init(&gic_options); krb5_get_init_creds_opt_set_tkt_life(&gic_options, TKT_LIFETIME); err = krb5_get_init_creds_password(context, &credentials, user_principal, password, 0, 0, 0, sprinc, &gic_options); switch (err) { case 0: /* Success. */ have_credentials = 1; break; case KRB5KDC_ERR_PREAUTH_FAILED: case KRB5KRB_AP_ERR_BAD_INTEGRITY: case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN: /* Bad username or password, unknown user etc */ success = 0; /* fall through */ default: /* Some other sort of failure. */ FAIL(err, "from krb5_get_init_creds_password"); break; } err = krb5_cc_store_cred(context, ccache, &credentials); FAIL(err, "from krb5_cc_store_cred"); err = krb5_sname_to_principal(context, host, service, KRB5_NT_SRV_HST, &service_principal); FAIL(err, "from krb5_sname_to_principal"); have_service_principal = 1; if (kt_pathname && *kt_pathname) { err = krb5_kt_resolve(context, kt_pathname, &keytab); FAIL(err, "from krb5_kt_resolve"); have_keytab = 1; } #ifndef NO_REPLAYCACHE krb5_verify_init_creds_opt_init(&vic_options); krb5_verify_init_creds_opt_set_ap_req_nofail(&vic_options, 1); err = krb5_verify_init_creds(context, &credentials, service_principal, keytab, 0, &vic_options); FAIL(err, "from krb5_verify_init_creds"); #else err = krb5_mk_req(context, &auth_context, 0, service, host, NULL, ccache, &apreq_pkt); FAIL(err, "from krb5_mk_req"); if (auth_context) { krb5_auth_con_free(context, auth_context); auth_context = NULL; } err = krb5_auth_con_init(context, &auth_context); FAIL(err, "from krb5_auth_con_init"); err = krb5_auth_con_setflags(context, auth_context, ~KRB5_AUTH_CONTEXT_DO_TIME); FAIL(err, "from krb5_auth_con_setflags"); err = krb5_rd_req(context, &auth_context, &apreq_pkt, service_principal, keytab, NULL, NULL); FAIL(err, "from krb5_rd_req"); if (auth_context) { krb5_auth_con_free(context, auth_context); auth_context = NULL; } #endif /* NO_REPLAYCACHE */ err = krb5_cc_destroy(context, ccache); FAIL(err, "from krb5_cc_destroy"); success = 1; cleanup: if (apreq_pkt.data) krb5_free_data_contents(context, &apreq_pkt); if (have_keytab) if (err = krb5_kt_close(context, keytab)) com_err("kpass", err, "from krb5_kt_close"); if (have_user_principal) krb5_free_principal(context, user_principal); if (have_service_principal) krb5_free_principal(context, service_principal); if (have_credentials) krb5_free_cred_contents(context, &credentials); if (context) krb5_free_context(context); return success; }