diff options
Diffstat (limited to 'usr/src/lib/smbsrv/libsmbns/common/smbns_krb.c')
-rw-r--r-- | usr/src/lib/smbsrv/libsmbns/common/smbns_krb.c | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_krb.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_krb.c new file mode 100644 index 0000000000..c1a2de7913 --- /dev/null +++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_krb.c @@ -0,0 +1,543 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Copyright 1990 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * + * Initialize a credentials cache. + */ +#include <kerberosv5/krb5.h> +#include <kerberosv5/com_err.h> +#include <string.h> +#include <stdio.h> +#include <time.h> +#include <netdb.h> +#include <syslog.h> +#include <locale.h> +#include <strings.h> +#include <sys/synch.h> +#include <gssapi/gssapi.h> + +#include <smbsrv/libsmbns.h> + +#include <smbns_krb.h> + +static int krb5_acquire_cred_kinit_main(); + +typedef enum { INIT_PW, INIT_KT, RENEW, VALIDATE } action_type; + +struct k_opts { + /* in seconds */ + krb5_deltat starttime; + krb5_deltat lifetime; + krb5_deltat rlife; + + int forwardable; + int proxiable; + int addresses; + + int not_forwardable; + int not_proxiable; + int no_addresses; + + int verbose; + + char *principal_name; + char *principal_passwd; + char *service_name; + char *keytab_name; + char *k5_cache_name; + char *k4_cache_name; + + action_type action; +}; + +struct k5_data { + krb5_context ctx; + krb5_ccache cc; + krb5_principal me; + char *name; +}; + +static int +k5_begin(struct k_opts *opts, struct k5_data *k5) +{ + int code; + code = krb5_init_context(&k5->ctx); + if (code) { + return (code); + } + + if ((code = krb5_cc_default(k5->ctx, &k5->cc))) { + return (code); + } + + /* Use specified name */ + if ((code = krb5_parse_name(k5->ctx, opts->principal_name, &k5->me))) { + return (code); + } + + code = krb5_unparse_name(k5->ctx, k5->me, &k5->name); + if (code) { + return (code); + } + opts->principal_name = k5->name; + + return (0); +} + +static void +k5_end(struct k5_data *k5) +{ + if (k5->name) + krb5_free_unparsed_name(k5->ctx, k5->name); + if (k5->me) + krb5_free_principal(k5->ctx, k5->me); + if (k5->cc) + krb5_cc_close(k5->ctx, k5->cc); + if (k5->ctx) + krb5_free_context(k5->ctx); + (void) memset(k5, 0, sizeof (*k5)); +} + +static int +k5_kinit(struct k_opts *opts, struct k5_data *k5) +{ + int notix = 1; + krb5_keytab keytab = 0; + krb5_creds my_creds; + krb5_error_code code = 0; + krb5_get_init_creds_opt options; + const char *errmsg; + + krb5_get_init_creds_opt_init(&options); + (void) memset(&my_creds, 0, sizeof (my_creds)); + + /* + * From this point on, we can goto cleanup because my_creds is + * initialized. + */ + if (opts->lifetime) + krb5_get_init_creds_opt_set_tkt_life(&options, opts->lifetime); + if (opts->rlife) + krb5_get_init_creds_opt_set_renew_life(&options, opts->rlife); + if (opts->forwardable) + krb5_get_init_creds_opt_set_forwardable(&options, 1); + if (opts->not_forwardable) + krb5_get_init_creds_opt_set_forwardable(&options, 0); + if (opts->proxiable) + krb5_get_init_creds_opt_set_proxiable(&options, 1); + if (opts->not_proxiable) + krb5_get_init_creds_opt_set_proxiable(&options, 0); + if (opts->addresses) { + krb5_address **addresses = NULL; + code = krb5_os_localaddr(k5->ctx, &addresses); + if (code != 0) { + errmsg = error_message(code); + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: " + "getting local addresses (%s)"), errmsg); + goto cleanup; + } + krb5_get_init_creds_opt_set_address_list(&options, addresses); + } + if (opts->no_addresses) + krb5_get_init_creds_opt_set_address_list(&options, NULL); + + if ((opts->action == INIT_KT) && opts->keytab_name) { + code = krb5_kt_resolve(k5->ctx, opts->keytab_name, &keytab); + if (code != 0) { + errmsg = error_message(code); + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: " + "resolving keytab %s (%s)"), errmsg, + opts->keytab_name); + goto cleanup; + } + } + + switch (opts->action) { + case INIT_PW: + code = krb5_get_init_creds_password(k5->ctx, &my_creds, k5->me, + opts->principal_passwd, NULL, 0, opts->starttime, + opts->service_name, &options); + break; + case INIT_KT: + code = krb5_get_init_creds_keytab(k5->ctx, &my_creds, k5->me, + keytab, opts->starttime, opts->service_name, &options); + break; + case VALIDATE: + code = krb5_get_validated_creds(k5->ctx, &my_creds, k5->me, + k5->cc, opts->service_name); + break; + case RENEW: + code = krb5_get_renewed_creds(k5->ctx, &my_creds, k5->me, + k5->cc, opts->service_name); + break; + } + + if (code) { + char *doing = 0; + switch (opts->action) { + case INIT_PW: + case INIT_KT: + doing = dgettext(TEXT_DOMAIN, "k5_kinit: " + "getting initial credentials"); + break; + case VALIDATE: + doing = dgettext(TEXT_DOMAIN, "k5_kinit: " + "validating credentials"); + break; + case RENEW: + doing = dgettext(TEXT_DOMAIN, "k5_kinit: " + "renewing credentials"); + break; + } + + /* + * If got code == KRB5_AP_ERR_V4_REPLY && got_k4, we should + * let the user know that maybe he/she wants -4. + */ + if (code == KRB5KRB_AP_ERR_V4_REPLY) { + syslog(LOG_ERR, "%s\n" + "The KDC doesn't support v5. " + "You may want the -4 option in the future", doing); + return (1); + } else if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY) { + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "%s " + "(Password incorrect)"), doing); + } else { + errmsg = error_message(code); + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "%s (%s)"), + doing, errmsg); + } + goto cleanup; + } + + if (!opts->lifetime) { + /* We need to figure out what lifetime to use for Kerberos 4. */ + opts->lifetime = my_creds.times.endtime - + my_creds.times.authtime; + } + + code = krb5_cc_initialize(k5->ctx, k5->cc, k5->me); + if (code) { + errmsg = error_message(code); + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: " + "initializing cache %s (%s)"), + opts->k5_cache_name?opts->k5_cache_name:"", errmsg); + goto cleanup; + } + + code = krb5_cc_store_cred(k5->ctx, k5->cc, &my_creds); + if (code) { + errmsg = error_message(code); + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: " + "storing credentials (%s)"), errmsg); + goto cleanup; + } + + notix = 0; + + cleanup: + if (my_creds.client == k5->me) { + my_creds.client = 0; + } + krb5_free_cred_contents(k5->ctx, &my_creds); + if (keytab) + krb5_kt_close(k5->ctx, keytab); + return (notix?0:1); +} + +int +smb_kinit(char *user, char *passwd) +{ + struct k_opts opts; + struct k5_data k5; + int authed_k5 = 0; + + (void) memset(&opts, 0, sizeof (opts)); + opts.action = INIT_PW; + opts.principal_name = strdup(user); + opts.principal_passwd = strdup(passwd); + + (void) memset(&k5, 0, sizeof (k5)); + + if (k5_begin(&opts, &k5) != 0) { + syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "smb_kinit: " + "NOT Authenticated to Kerberos v5 k5_begin failed\n")); + return (0); + } + + authed_k5 = k5_kinit(&opts, &k5); + if (authed_k5) { + syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, "smb_kinit: " + "Authenticated to Kerberos v5\n")); + } else { + syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, "smb_kinit: " + "NOT Authenticated to Kerberos v5\n")); + } + + k5_end(&k5); + + return (authed_k5); +} + +/* + * krb5_display_stat + * Display error message for GSS-API routines. + * Parameters: + * maj : GSS major status + * min : GSS minor status + * caller_mod: module name that calls this routine so that the module name + * can be displayed with the error messages + * Returns: + * None + */ +static void +krb5_display_stat(OM_uint32 maj, OM_uint32 min, char *caller_mod) +{ + gss_buffer_desc msg; + OM_uint32 msg_ctx = 0; + OM_uint32 min2; + (void) gss_display_status(&min2, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID, + &msg_ctx, &msg); + syslog(LOG_ERR, "%s: major status error: %s\n", + caller_mod, (char *)msg.value); + (void) gss_display_status(&min2, min, GSS_C_MECH_CODE, GSS_C_NULL_OID, + &msg_ctx, &msg); + syslog(LOG_ERR, "%s: minor status error: %s\n", + caller_mod, (char *)msg.value); +} + +/* + * krb5_acquire_cred_kinit + * + * Wrapper for krb5_acquire_cred_kinit_main with mutex to protect credential + * cache file when calling krb5_acquire_cred or kinit. + */ + +int +krb5_acquire_cred_kinit(char *user, char *pwd, gss_cred_id_t *cred_handle, + gss_OID *oid, int *kinit_retry, char *caller_mod) +{ + int ret; + + ret = krb5_acquire_cred_kinit_main(user, pwd, + cred_handle, oid, kinit_retry, caller_mod); + return (ret); +} + +/* + * krb5_acquire_cred_kinit_main + * + * This routine is called both by ADS and Dyn DNS modules to get a handle to + * administrative user's credential stored locally on the system. The + * credential is the TGT. If the attempt at getting handle fails then a second + * attempt will be made after getting a new TGT. + * + * Paramters: + * user : username to retrieve a handle to its credential + * pwd : password of username in case obtaining a new TGT is needed + * kinit_retry: if 0 then a second attempt will be made to get handle to the + * credential if the first attempt fails + * caller_mod : name of module that call this routine so that the module name + * can be included with error messages + * Returns: + * cred_handle: handle to the administrative user's credential (TGT) + * oid : contains Kerberos 5 object identifier + * kinit_retry: A 1 indicates that a second attempt has been made to get + * handle to the credential and no further attempts can be made + * -1 : error + * 0 : success + */ +static int +krb5_acquire_cred_kinit_main(char *user, char *pwd, gss_cred_id_t *cred_handle, + gss_OID *oid, int *kinit_retry, char *caller_mod) +{ + OM_uint32 maj, min; + gss_name_t desired_name; + gss_OID_set desired_mechs; + gss_buffer_desc oidstr, name_buf; + char str[50], user_name[50]; + + acquire_cred: + + /* Object Identifier for Kerberos 5 */ + (void) strcpy(str, "{ 1 2 840 113554 1 2 2 }"); + oidstr.value = str; + oidstr.length = strlen(str); + if ((maj = gss_str_to_oid(&min, &oidstr, oid)) != GSS_S_COMPLETE) { + krb5_display_stat(maj, min, caller_mod); + return (-1); + } + if ((maj = gss_create_empty_oid_set(&min, &desired_mechs)) + != GSS_S_COMPLETE) { + krb5_display_stat(maj, min, caller_mod); + (void) gss_release_oid(&min, oid); + return (-1); + } + if ((maj = gss_add_oid_set_member(&min, *oid, &desired_mechs)) + != GSS_S_COMPLETE) { + krb5_display_stat(maj, min, caller_mod); + (void) gss_release_oid(&min, oid); + (void) gss_release_oid_set(&min, &desired_mechs); + return (-1); + } + + (void) strcpy(user_name, user); + name_buf.value = user_name; + name_buf.length = strlen(user_name)+1; + if ((maj = gss_import_name(&min, &name_buf, GSS_C_NT_USER_NAME, + &desired_name)) != GSS_S_COMPLETE) { + krb5_display_stat(maj, min, caller_mod); + (void) gss_release_oid(&min, oid); + (void) gss_release_oid_set(&min, &desired_mechs); + return (-1); + } + + if ((maj = gss_acquire_cred(&min, desired_name, 0, desired_mechs, + GSS_C_INITIATE, cred_handle, NULL, NULL)) != GSS_S_COMPLETE) { + if (!*kinit_retry) { + (void) gss_release_oid(&min, oid); + (void) gss_release_oid_set(&min, &desired_mechs); + (void) gss_release_name(&min, &desired_name); + syslog(LOG_ERR, "%s: Retry kinit to " + "acquire credential.\n", caller_mod); + (void) smb_kinit(user, pwd); + *kinit_retry = 1; + goto acquire_cred; + } else { + krb5_display_stat(maj, min, caller_mod); + (void) gss_release_oid(&min, oid); + (void) gss_release_oid_set(&min, &desired_mechs); + (void) gss_release_name(&min, &desired_name); + return (-1); + } + } + (void) gss_release_oid_set(&min, &desired_mechs); + (void) gss_release_name(&min, &desired_name); + + return (0); +} + +/* + * krb5_establish_sec_ctx_kinit + * + * This routine is called by both the ADS and Dyn DNS modules to establish a + * security context before ADS or Dyn DNS updates are allowed. If establishing + * a security context fails for any reason, a second attempt will be made after + * a new TGT is obtained. This routine is called many time as needed until + * a security context is established. + * + * The resources use for the security context must be released if security + * context establishment process fails. + * Parameters: + * user : user used in establishing a security context for. Is used for + * obtaining a new TGT for a second attempt at establishing + * security context + * pwd : password of above user + * cred_handle: a handle to the user credential (TGT) stored locally + * gss_context: initially set to GSS_C_NO_CONTEXT but will contain a handle + * to a security context + * target_name: contains service name to establish a security context with, + * ie ldap or dns + * gss_flags : flags used in establishing security context + * inputptr : initially set to GSS_C_NO_BUFFER but will be token data + * received from service's server to be processed to generate + * further token to be sent back to service's server during + * security context establishment + * kinit_retry: if 0 then a second attempt will be made to get handle to the + * credential if the first attempt fails + * caller_mod : name of module that call this routine so that the module name + * can be included with error messages + * Returns: + * gss_context : a handle to a security context + * out_tok : token data to be sent to service's server to establish + * security context + * ret_flags : return flags + * time_rec : valid time for security context, not currently used + * kinit_retry : A 1 indicates that a second attempt has been made to get + * handle to the credential and no further attempts can be + * made + * do_acquire_cred: A 1 indicates that a new handle to the local credential + * is needed for second attempt at security context + * establishment + * maj : major status code used if determining is security context + * establishment is successful + */ +int +krb5_establish_sec_ctx_kinit(char *user, char *pwd, + gss_cred_id_t cred_handle, gss_ctx_id_t *gss_context, + gss_name_t target_name, gss_OID oid, int gss_flags, + gss_buffer_desc *inputptr, gss_buffer_desc* out_tok, + OM_uint32 *ret_flags, OM_uint32 *time_rec, + int *kinit_retry, int *do_acquire_cred, + OM_uint32 *maj, char *caller_mod) +{ + OM_uint32 min; + + *maj = gss_init_sec_context(&min, cred_handle, gss_context, + target_name, oid, gss_flags, 0, NULL, inputptr, NULL, + out_tok, ret_flags, time_rec); + if (*maj != GSS_S_COMPLETE && *maj != GSS_S_CONTINUE_NEEDED) { + if (*gss_context != NULL) + (void) gss_delete_sec_context(&min, gss_context, NULL); + + if (!*kinit_retry) { + syslog(LOG_ERR, "%s: Retry kinit to establish " + "security context.\n", caller_mod); + (void) smb_kinit(user, pwd); + *kinit_retry = 1; + *do_acquire_cred = 1; + return (-1); + } else { + krb5_display_stat(*maj, min, caller_mod); + return (-1); + } + } + return (0); +} |