[PATCH] cifs.upcall: fix krb5 regression with --trust-dns
Paulo Alcantara <[email protected]>
| Newsgroups | org.kernel.vger.linux-cifs |
|---|---|
| Message-ID | <[email protected]> |
Customer reported randomly failed krb5 authentications against certain servers when using --trust-dns parameter. Turned out that the client failed to establish sessions against those servers when using CIFS/KRB5_NT_SRV_HST service principals. Restore old behavior of always trying cifs/KRB5_NT_UNKNOWN service principal first when using --trust-dns parameter. Reported-by: Pierguido Lambri <[email protected]> Reported-by: Frank Sorenson <[email protected]> Fixes: 562a6cb4f99c ("cifs.upcall: Retry krb5 TGS request with uppercase service name") Signed-off-by: Paulo Alcantara (Red Hat) <[email protected]> Cc: Steve French <[email protected]> Cc: Samuel Cabrero <[email protected]> Cc: [email protected] --- cifs.upcall.c | 281 +++++++++++++++++++++++++------------------------- 1 file changed, 140 insertions(+), 141 deletions(-) diff --git a/cifs.upcall.c b/cifs.upcall.c index 42205e66a676..76e88b79a760 100644 --- a/cifs.upcall.c +++ b/cifs.upcall.c @@ -698,22 +698,18 @@ out_free_principal: return rc; } -static int -cifs_krb5_get_req(const char *host, krb5_ccache ccache, - DATA_BLOB * mechtoken, DATA_BLOB * sess_key) +static int cifs_krb5_get_req(const struct cifs_service_name *svc, + const char *host, krb5_ccache ccache, + DATA_BLOB *mechtoken, DATA_BLOB *sess_key) { krb5_error_code ret; krb5_keyblock *tokb; - krb5_creds in_creds, *out_creds; + krb5_creds in_creds = {}, *out_creds; krb5_data apreq_pkt, in_data; krb5_auth_context auth_context = NULL; #if defined(HAVE_KRB5_AUTH_CON_SETADDRS) && defined(HAVE_KRB5_AUTH_CON_SET_REQ_CKSUMTYPE) static char gss_cksum[24] = { 0x10, 0x00, /* ... */}; #endif - size_t count = sizeof(cifs_service_names) / sizeof(struct cifs_service_name); - size_t i = 0; - - memset(&in_creds, 0, sizeof(in_creds)); ret = krb5_cc_get_principal(context, ccache, &in_creds.client); if (ret) { @@ -722,21 +718,16 @@ cifs_krb5_get_req(const char *host, krb5_ccache ccache, return ret; } - for (i = 0; i < count; i++) { - ret = krb5_sname_to_principal(context, host, cifs_service_names[i].name, - cifs_service_names[i].type, &in_creds.server); - if (ret) { - syslog(LOG_DEBUG, "%s: unable to convert sname to princ (%s).", - __func__, host); - goto out_free_principal; - } - - ret = krb5_get_credentials(context, 0, ccache, &in_creds, &out_creds); - krb5_free_principal(context, in_creds.server); - if (!ret) { - break; - } + ret = krb5_sname_to_principal(context, host, svc->name, + svc->type, &in_creds.server); + if (ret) { + syslog(LOG_DEBUG, "%s: unable to convert sname to princ (%s).", + __func__, host); + goto out_free_principal; } + + ret = krb5_get_credentials(context, 0, ccache, &in_creds, &out_creds); + krb5_free_principal(context, in_creds.server); if (ret) { syslog(LOG_DEBUG, "%s: unable to get credentials for %s", __func__, host); @@ -849,8 +840,9 @@ void cifs_gss_display_status(char *msg, OM_uint32 maj_stat, OM_uint32 min_stat) cifs_gss_display_status_1(msg, min_stat, GSS_C_MECH_CODE); } -static int -cifs_gss_get_req(const char *host, DATA_BLOB *mechtoken, DATA_BLOB *sess_key) +static int cifs_gss_get_req(const struct cifs_service_name *svc, + const char *host, DATA_BLOB *mechtoken, + DATA_BLOB *sess_key) { OM_uint32 maj_stat, min_stat; gss_name_t target_name; @@ -858,52 +850,43 @@ cifs_gss_get_req(const char *host, DATA_BLOB *mechtoken, DATA_BLOB *sess_key) gss_buffer_desc output_token; gss_krb5_lucid_context_v1_t *lucid_ctx = NULL; gss_krb5_lucid_key_t *key = NULL; - size_t count = sizeof(cifs_service_names) / sizeof(struct cifs_service_name); - size_t i; + gss_buffer_desc target_name_buf; + size_t service_name_len; + char *service_name; - for (i = 0; i < count; i++) { - size_t service_name_len = strlen(cifs_service_names[i].name) + - 1 /* @ */ + strlen(host) + 1; - char *service_name = malloc(service_name_len); - if (!service_name) { - syslog(LOG_DEBUG, "out of memory allocating service name"); - maj_stat = GSS_S_FAILURE; - goto out; - } - - snprintf(service_name, service_name_len, "%s@%s", - cifs_service_names[i].name, host); - gss_buffer_desc target_name_buf; - target_name_buf.value = service_name; - target_name_buf.length = service_name_len; - - maj_stat = gss_import_name(&min_stat, &target_name_buf, - GSS_C_NT_HOSTBASED_SERVICE, &target_name); - free(service_name); - if (GSS_ERROR(maj_stat)) { - cifs_gss_display_status("gss_import_name", maj_stat, min_stat); - goto out; - } + service_name_len = strlen(svc->name) + 1 /* @ */ + strlen(host) + 1; + service_name = malloc(service_name_len); + if (!service_name) { + syslog(LOG_DEBUG, "out of memory allocating service name"); + maj_stat = GSS_S_FAILURE; + goto out; + } - maj_stat = gss_init_sec_context(&min_stat, - GSS_C_NO_CREDENTIAL, /* claimant_cred_handle */ - &ctx, - target_name, - discard_const(gss_mech_krb5), /* force krb5 */ - 0, /* flags */ - 0, /* time_req */ - GSS_C_NO_CHANNEL_BINDINGS, /* input_chan_bindings */ - GSS_C_NO_BUFFER, - NULL, /* actual mech type */ - &output_token, - NULL, /* ret_flags */ - NULL); /* time_rec */ + snprintf(service_name, service_name_len, "%s@%s", svc->name, host); - if (maj_stat == GSS_S_COMPLETE || maj_stat == GSS_S_CONTINUE_NEEDED) { - break; - } - (void) gss_release_name(&min_stat, &target_name); + target_name_buf.value = service_name; + target_name_buf.length = service_name_len; + maj_stat = gss_import_name(&min_stat, &target_name_buf, + GSS_C_NT_HOSTBASED_SERVICE, &target_name); + free(service_name); + if (GSS_ERROR(maj_stat)) { + cifs_gss_display_status("gss_import_name", maj_stat, min_stat); + goto out; } + + maj_stat = gss_init_sec_context(&min_stat, + GSS_C_NO_CREDENTIAL, /* claimant_cred_handle */ + &ctx, + target_name, + discard_const(gss_mech_krb5), /* force krb5 */ + 0, /* flags */ + 0, /* time_req */ + GSS_C_NO_CHANNEL_BINDINGS, /* input_chan_bindings */ + GSS_C_NO_BUFFER, + NULL, /* actual mech type */ + &output_token, + NULL, /* ret_flags */ + NULL); /* time_rec */ if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) { cifs_gss_display_status("init_sec_context", maj_stat, min_stat); @@ -914,8 +897,7 @@ cifs_gss_get_req(const char *host, DATA_BLOB *mechtoken, DATA_BLOB *sess_key) *mechtoken = data_blob(output_token.value, output_token.length); maj_stat = gss_krb5_export_lucid_sec_context(&min_stat, &ctx, 1, - (void **)&lucid_ctx); - + (void **)&lucid_ctx); if (GSS_ERROR(maj_stat)) { cifs_gss_display_status("gss_krb5_export_lucid_sec_context", maj_stat, min_stat); @@ -972,9 +954,9 @@ out: * * ret: 0 - success, others - failure */ -static int -handle_krb5_mech(const char *oid, const char *host, DATA_BLOB * secblob, - DATA_BLOB * sess_key, krb5_ccache ccache) +static int handle_krb5_mech(const char *oid, const struct cifs_service_name *svc, + const char *host, DATA_BLOB *secblob, + DATA_BLOB *sess_key, krb5_ccache ccache) { int retval; DATA_BLOB tkt_wrapped; @@ -987,7 +969,7 @@ handle_krb5_mech(const char *oid, const char *host, DATA_BLOB * secblob, */ if (!ccache) { syslog(LOG_DEBUG, "%s: using GSS-API", __func__); - retval = cifs_gss_get_req(host, &tkt_wrapped, sess_key); + retval = cifs_gss_get_req(svc, host, &tkt_wrapped, sess_key); if (retval) { syslog(LOG_DEBUG, "%s: failed to obtain service ticket via GSS (%d)", __func__, retval); @@ -998,7 +980,7 @@ handle_krb5_mech(const char *oid, const char *host, DATA_BLOB * secblob, syslog(LOG_DEBUG, "%s: using native krb5", __func__); /* get a kerberos ticket for the service and extract the session key */ - retval = cifs_krb5_get_req(host, ccache, &tkt, sess_key); + retval = cifs_krb5_get_req(svc, host, ccache, &tkt, sess_key); if (retval) { syslog(LOG_DEBUG, "%s: failed to obtain service ticket (%d)", __func__, retval); @@ -1570,6 +1552,83 @@ static const struct option long_options[] = { {NULL, 0, NULL, 0} }; +static long get_spn_key_from_host(const struct decoded_args *arg, + const struct cifs_service_name *svc, + char *host, DATA_BLOB *secblob, + DATA_BLOB *sess_key, bool try_dns, + krb5_ccache ccache) +{ + char hostbuf[NI_MAXHOST] = {}; + const char *oid; + long rc; + + if (arg->sec == MS_KRB5) + oid = OID_KERBEROS5_OLD; + else + oid = OID_KERBEROS5; + +retry_new_hostname: + lowercase_string(host); + rc = handle_krb5_mech(oid, svc, host, secblob, sess_key, ccache); + if (!rc) + return rc; + + /* + * If hostname has a '.', assume it's a FQDN, otherwise we + * want to guess the domainname. + */ + if (!strchr(host, '.')) { + struct addrinfo hints = {}; + struct addrinfo *ai; + char *domainname; + char fqdn[NI_MAXHOST]; + + /* + * use getaddrinfo() to resolve the hostname of the + * server and set ai_canonname. + */ + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_CANONNAME; + rc = getaddrinfo(host, NULL, &hints, &ai); + if (rc) { + syslog(LOG_ERR, "Unable to resolve host address: %s [%s]", + host, gai_strerror(rc)); + return rc; + } + + /* scan forward to first '.' in ai_canonnname */ + domainname = strchr(ai->ai_canonname, '.'); + if (!domainname) { + rc = -EINVAL; + freeaddrinfo(ai); + return rc; + } + lowercase_string(domainname); + rc = snprintf(fqdn, sizeof(fqdn), "%s%s", + host, domainname); + freeaddrinfo(ai); + if (rc < 0 || (size_t)rc >= sizeof(fqdn)) { + syslog(LOG_ERR, "Problem setting hostname in string: %ld", rc); + rc = -EINVAL; + return rc; + } + + rc = handle_krb5_mech(oid, svc, fqdn, secblob, sess_key, ccache); + if (!rc) + return rc; + } + + if (try_dns && (arg->have & DKD_HAVE_IP)) { + rc = ip_to_fqdn(arg->ip, hostbuf, sizeof(hostbuf)); + if (rc) + return rc; + try_dns = false; + host = hostbuf; + goto retry_new_hostname; + } + return rc; +} + int main(const int argc, char *const argv[]) { struct cifs_spnego_msg *keydata = NULL; @@ -1582,9 +1641,8 @@ int main(const int argc, char *const argv[]) int mask; bool try_dns = false, legacy_uid = false , env_probe = true; char *buf; - char hostbuf[NI_MAXHOST], *host; + char *host; struct decoded_args *arg = NULL; - const char *oid; uid_t uid; gid_t gid; struct passwd *pw; @@ -1593,8 +1651,8 @@ int main(const int argc, char *const argv[]) krb5_ccache ccache = NULL; unsigned expire_time = DNS_RESOLVER_DEFAULT_TIMEOUT; const char *key_descr = NULL; - - hostbuf[0] = '\0'; + size_t i; + size_t num_spns = sizeof(cifs_service_names) / sizeof(cifs_service_names[0]); openlog(prog, 0, LOG_DAEMON); mask = LOG_UPTO(LOG_ERR); @@ -1864,73 +1922,14 @@ int main(const int argc, char *const argv[]) * TRY only: * cifs/bar.example.com@REALM */ - if (arg->sec == MS_KRB5) - oid = OID_KERBEROS5_OLD; - else - oid = OID_KERBEROS5; - -retry_new_hostname: - lowercase_string(host); - rc = handle_krb5_mech(oid, host, &secblob, &sess_key, ccache); - if (!rc) - break; - - /* - * If hostname has a '.', assume it's a FQDN, otherwise we - * want to guess the domainname. - */ - if (!strchr(host, '.')) { - struct addrinfo hints; - struct addrinfo *ai; - char *domainname; - char fqdn[NI_MAXHOST]; - - /* - * use getaddrinfo() to resolve the hostname of the - * server and set ai_canonname. - */ - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_flags = AI_CANONNAME; - rc = getaddrinfo(host, NULL, &hints, &ai); - if (rc) { - syslog(LOG_ERR, "Unable to resolve host address: %s [%s]", - host, gai_strerror(rc)); - break; - } - - /* scan forward to first '.' in ai_canonnname */ - domainname = strchr(ai->ai_canonname, '.'); - if (!domainname) { - rc = -EINVAL; - freeaddrinfo(ai); - break; - } - lowercase_string(domainname); - rc = snprintf(fqdn, sizeof(fqdn), "%s%s", - host, domainname); - freeaddrinfo(ai); - if (rc < 0 || (size_t)rc >= sizeof(fqdn)) { - syslog(LOG_ERR, "Problem setting hostname in string: %ld", rc); - rc = -EINVAL; - break; - } - - rc = handle_krb5_mech(oid, fqdn, &secblob, &sess_key, ccache); + for (i = 0; i < num_spns; i++) { + rc = get_spn_key_from_host(arg, &cifs_service_names[i], + host, &secblob, &sess_key, + try_dns, ccache); if (!rc) break; } - - if (!try_dns || !(arg->have & DKD_HAVE_IP)) - break; - - rc = ip_to_fqdn(arg->ip, hostbuf, sizeof(hostbuf)); - if (rc) - break; - - try_dns = false; - host = hostbuf; - goto retry_new_hostname; + break; default: syslog(LOG_ERR, "sectype: %d is not implemented", arg->sec); rc = 1; -- 2.55.0