+ krb5_error_code smb_krb5_get_init_creds_opt_alloc(krb5_context context,
+ krb5_get_init_creds_opt **opt)
+{
+#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC
+ /* Heimdal or modern MIT version */
+ return krb5_get_init_creds_opt_alloc(context, opt);
+#else
+ /* Historical MIT version */
+ krb5_get_init_creds_opt *my_opt;
+
+ *opt = NULL;
+
+ if ((my_opt = SMB_MALLOC_P(krb5_get_init_creds_opt)) == NULL) {
+ return ENOMEM;
+ }
+
+ krb5_get_init_creds_opt_init(my_opt);
+
+ *opt = my_opt;
+ return 0;
+#endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC */
+}
+
+ void smb_krb5_get_init_creds_opt_free(krb5_context context,
+ krb5_get_init_creds_opt *opt)
+{
+#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_FREE
+
+#ifdef KRB5_CREDS_OPT_FREE_REQUIRES_CONTEXT
+ /* Modern MIT or Heimdal version */
+ krb5_get_init_creds_opt_free(context, opt);
+#else
+ /* Heimdal version */
+ krb5_get_init_creds_opt_free(opt);
+#endif /* KRB5_CREDS_OPT_FREE_REQUIRES_CONTEXT */
+
+#else /* HAVE_KRB5_GET_INIT_CREDS_OPT_FREE */
+ /* Historical MIT version */
+ SAFE_FREE(opt);
+ opt = NULL;
+#endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_FREE */
+}
+
+ krb5_enctype smb_get_enctype_from_kt_entry(krb5_keytab_entry *kt_entry)
+{
+ return KRB5_KEY_TYPE(KRB5_KT_KEY(kt_entry));
+}
+
+
+/* caller needs to free etype_s */
+ krb5_error_code smb_krb5_enctype_to_string(krb5_context context,
+ krb5_enctype enctype,
+ char **etype_s)
+{
+#ifdef HAVE_KRB5_ENCTYPE_TO_STRING_WITH_KRB5_CONTEXT_ARG
+ return krb5_enctype_to_string(context, enctype, etype_s); /* Heimdal */
+#elif defined(HAVE_KRB5_ENCTYPE_TO_STRING_WITH_SIZE_T_ARG)
+ char buf[256];
+ krb5_error_code ret = krb5_enctype_to_string(enctype, buf, 256); /* MIT */
+ if (ret) {
+ return ret;
+ }
+ *etype_s = SMB_STRDUP(buf);
+ if (!*etype_s) {
+ return ENOMEM;
+ }
+ return ret;
+#else
+#error UNKNOWN_KRB5_ENCTYPE_TO_STRING_FUNCTION
+#endif
+}
+
+ krb5_error_code smb_krb5_mk_error(krb5_context context,
+ krb5_error_code error_code,
+ const krb5_principal server,
+ krb5_data *reply)
+{
+#ifdef HAVE_SHORT_KRB5_MK_ERROR_INTERFACE /* MIT */
+ /*
+ * The MIT interface is *terrible*.
+ * We have to construct this ourselves...
+ */
+ krb5_error e;
+
+ memset(&e, 0, sizeof(e));
+ krb5_us_timeofday(context, &e.stime, &e.susec);
+ e.server = server;
+#if defined(krb5_err_base)
+ e.error = error_code - krb5_err_base;
+#elif defined(ERROR_TABLE_BASE_krb5)
+ e.error = error_code - ERROR_TABLE_BASE_krb5;
+#else
+ e.error = error_code; /* Almost certainly wrong, but what can we do... ? */
+#endif
+
+ return krb5_mk_error(context, &e, reply);
+#else /* Heimdal. */
+ return krb5_mk_error(context,
+ error_code,
+ NULL,
+ NULL, /* e_data */
+ NULL,
+ server,
+ NULL,
+ NULL,
+ reply);
+#endif
+}
+
+/**********************************************************************
+ * Open a krb5 keytab with flags, handles readonly or readwrite access and
+ * allows to process non-default keytab names.
+ * @param context krb5_context
+ * @param keytab_name_req string
+ * @param write_access bool if writable keytab is required
+ * @param krb5_keytab pointer to krb5_keytab (close with krb5_kt_close())
+ * @return krb5_error_code
+**********************************************************************/
+
+/* This MAX_NAME_LEN is a constant defined in krb5.h */
+#ifndef MAX_KEYTAB_NAME_LEN
+#define MAX_KEYTAB_NAME_LEN 1100
+#endif
+
+ krb5_error_code smb_krb5_open_keytab(krb5_context context,
+ const char *keytab_name_req,
+ bool write_access,
+ krb5_keytab *keytab)
+{
+ krb5_error_code ret = 0;
+ TALLOC_CTX *mem_ctx;
+ char keytab_string[MAX_KEYTAB_NAME_LEN];
+ char *kt_str = NULL;
+ bool found_valid_name = False;
+ const char *pragma = "FILE";
+ const char *tmp = NULL;
+
+ if (!write_access && !keytab_name_req) {
+ /* caller just wants to read the default keytab readonly, so be it */
+ return krb5_kt_default(context, keytab);
+ }
+
+ mem_ctx = talloc_init("smb_krb5_open_keytab");
+ if (!mem_ctx) {
+ return ENOMEM;
+ }
+
+#ifdef HAVE_WRFILE_KEYTAB
+ if (write_access) {
+ pragma = "WRFILE";
+ }
+#endif
+
+ if (keytab_name_req) {
+
+ if (strlen(keytab_name_req) > MAX_KEYTAB_NAME_LEN) {
+ ret = KRB5_CONFIG_NOTENUFSPACE;
+ goto out;
+ }
+
+ if ((strncmp(keytab_name_req, "WRFILE:/", 8) == 0) ||
+ (strncmp(keytab_name_req, "FILE:/", 6) == 0)) {
+ tmp = keytab_name_req;
+ goto resolve;
+ }
+
+ if (keytab_name_req[0] != '/') {
+ ret = KRB5_KT_BADNAME;
+ goto out;
+ }
+
+ tmp = talloc_asprintf(mem_ctx, "%s:%s", pragma, keytab_name_req);
+ if (!tmp) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ goto resolve;
+ }
+
+ /* we need to handle more complex keytab_strings, like:
+ * "ANY:FILE:/etc/krb5.keytab,krb4:/etc/srvtab" */
+
+ ret = krb5_kt_default_name(context, &keytab_string[0], MAX_KEYTAB_NAME_LEN - 2);
+ if (ret) {
+ goto out;
+ }
+
+ DEBUG(10,("smb_krb5_open_keytab: krb5_kt_default_name returned %s\n", keytab_string));
+
+ tmp = talloc_strdup(mem_ctx, keytab_string);
+ if (!tmp) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ if (strncmp(tmp, "ANY:", 4) == 0) {
+ tmp += 4;
+ }
+
+ memset(&keytab_string, '\0', sizeof(keytab_string));
+
+ while (next_token_talloc(mem_ctx, &tmp, &kt_str, ",")) {
+ if (strncmp(kt_str, "WRFILE:", 7) == 0) {
+ found_valid_name = True;
+ tmp = kt_str;
+ tmp += 7;
+ }
+
+ if (strncmp(kt_str, "FILE:", 5) == 0) {
+ found_valid_name = True;
+ tmp = kt_str;
+ tmp += 5;
+ }
+
+ if (tmp[0] == '/') {
+ /* Treat as a FILE: keytab definition. */
+ found_valid_name = true;
+ }
+
+ if (found_valid_name) {
+ if (tmp[0] != '/') {
+ ret = KRB5_KT_BADNAME;
+ goto out;
+ }
+
+ tmp = talloc_asprintf(mem_ctx, "%s:%s", pragma, tmp);
+ if (!tmp) {
+ ret = ENOMEM;
+ goto out;
+ }
+ break;
+ }
+ }
+
+ if (!found_valid_name) {
+ ret = KRB5_KT_UNKNOWN_TYPE;
+ goto out;
+ }
+
+ resolve:
+ DEBUG(10,("smb_krb5_open_keytab: resolving: %s\n", tmp));
+ ret = krb5_kt_resolve(context, tmp, keytab);
+
+ out:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+krb5_error_code smb_krb5_keytab_name(TALLOC_CTX *mem_ctx,
+ krb5_context context,
+ krb5_keytab keytab,
+ const char **keytab_name)
+{
+ char keytab_string[MAX_KEYTAB_NAME_LEN];
+ krb5_error_code ret = 0;
+
+ ret = krb5_kt_get_name(context, keytab,
+ keytab_string, MAX_KEYTAB_NAME_LEN - 2);
+ if (ret) {
+ return ret;
+ }
+
+ *keytab_name = talloc_strdup(mem_ctx, keytab_string);
+ if (!*keytab_name) {
+ return ENOMEM;
+ }
+
+ return ret;
+}
+
+#if defined(HAVE_KRB5_GET_CREDS_OPT_SET_IMPERSONATE) && \
+ defined(HAVE_KRB5_GET_CREDS_OPT_ALLOC) && \
+ defined(HAVE_KRB5_GET_CREDS)
+static krb5_error_code smb_krb5_get_credentials_for_user_opt(krb5_context context,
+ krb5_ccache ccache,
+ krb5_principal me,
+ krb5_principal server,
+ krb5_principal impersonate_princ,
+ krb5_creds **out_creds)
+{
+ krb5_error_code ret;
+ krb5_get_creds_opt opt;
+
+ ret = krb5_get_creds_opt_alloc(context, &opt);
+ if (ret) {
+ goto done;
+ }
+ krb5_get_creds_opt_add_options(context, opt, KRB5_GC_FORWARDABLE);
+
+ if (impersonate_princ) {
+ ret = krb5_get_creds_opt_set_impersonate(context, opt,
+ impersonate_princ);
+ if (ret) {
+ goto done;
+ }
+ }
+
+ ret = krb5_get_creds(context, opt, ccache, server, out_creds);
+ if (ret) {
+ goto done;
+ }
+
+ done:
+ if (opt) {
+ krb5_get_creds_opt_free(context, opt);
+ }
+ return ret;
+}
+#endif /* HAVE_KRB5_GET_CREDS_OPT_SET_IMPERSONATE */
+
+#ifdef HAVE_KRB5_GET_CREDENTIALS_FOR_USER
+static krb5_error_code smb_krb5_get_credentials_for_user(krb5_context context,
+ krb5_ccache ccache,
+ krb5_principal me,
+ krb5_principal server,
+ krb5_principal impersonate_princ,
+ krb5_creds **out_creds)
+{
+ krb5_error_code ret;
+ krb5_creds in_creds;
+
+#if !HAVE_DECL_KRB5_GET_CREDENTIALS_FOR_USER
+krb5_error_code KRB5_CALLCONV
+krb5_get_credentials_for_user(krb5_context context, krb5_flags options,
+ krb5_ccache ccache, krb5_creds *in_creds,
+ krb5_data *subject_cert,
+ krb5_creds **out_creds);
+#endif /* !HAVE_DECL_KRB5_GET_CREDENTIALS_FOR_USER */
+
+ ZERO_STRUCT(in_creds);
+
+ if (impersonate_princ) {
+
+ in_creds.server = me;
+ in_creds.client = impersonate_princ;
+
+ ret = krb5_get_credentials_for_user(context,
+ 0, /* krb5_flags options */
+ ccache,
+ &in_creds,
+ NULL, /* krb5_data *subject_cert */
+ out_creds);
+ } else {
+ in_creds.client = me;
+ in_creds.server = server;
+
+ ret = krb5_get_credentials(context, 0, ccache,
+ &in_creds, out_creds);
+ }
+
+ return ret;
+}
+#endif /* HAVE_KRB5_GET_CREDENTIALS_FOR_USER */
+
+/*
+ * smb_krb5_get_credentials
+ *
+ * @brief Get krb5 credentials for a server
+ *
+ * @param[in] context An initialized krb5_context
+ * @param[in] ccache An initialized krb5_ccache
+ * @param[in] me The krb5_principal of the caller
+ * @param[in] server The krb5_principal of the requested service
+ * @param[in] impersonate_princ The krb5_principal of a user to impersonate as (optional)
+ * @param[out] out_creds The returned krb5_creds structure
+ * @return krb5_error_code
+ *
+ */
+krb5_error_code smb_krb5_get_credentials(krb5_context context,
+ krb5_ccache ccache,
+ krb5_principal me,
+ krb5_principal server,
+ krb5_principal impersonate_princ,
+ krb5_creds **out_creds)
+{
+ krb5_error_code ret;
+ krb5_creds *creds = NULL;
+
+ *out_creds = NULL;
+
+ if (impersonate_princ) {
+#ifdef HAVE_KRB5_GET_CREDS_OPT_SET_IMPERSONATE /* Heimdal */
+ ret = smb_krb5_get_credentials_for_user_opt(context, ccache, me, server, impersonate_princ, &creds);
+#elif defined(HAVE_KRB5_GET_CREDENTIALS_FOR_USER) /* MIT */
+ ret = smb_krb5_get_credentials_for_user(context, ccache, me, server, impersonate_princ, &creds);
+#else
+ ret = ENOTSUP;
+#endif
+ } else {
+ krb5_creds in_creds;
+
+ ZERO_STRUCT(in_creds);
+
+ in_creds.client = me;
+ in_creds.server = server;
+
+ ret = krb5_get_credentials(context, 0, ccache,
+ &in_creds, &creds);
+ }
+ if (ret) {
+ goto done;
+ }
+
+ ret = krb5_cc_store_cred(context, ccache, creds);
+ if (ret) {
+ goto done;
+ }
+
+ if (out_creds) {
+ *out_creds = creds;
+ }
+
+ done:
+ if (creds && ret) {
+ krb5_free_creds(context, creds);
+ }
+
+ return ret;
+}
+
+/*
+ * smb_krb5_get_creds
+ *
+ * @brief Get krb5 credentials for a server
+ *
+ * @param[in] server_s The string name of the service
+ * @param[in] time_offset The offset to the KDCs time in seconds (optional)
+ * @param[in] cc The krb5 credential cache string name (optional)
+ * @param[in] impersonate_princ_s The string principal name to impersonate (optional)
+ * @param[out] creds_p The returned krb5_creds structure
+ * @return krb5_error_code
+ *
+ */
+krb5_error_code smb_krb5_get_creds(const char *server_s,
+ time_t time_offset,
+ const char *cc,
+ const char *impersonate_princ_s,
+ krb5_creds **creds_p)
+{
+ krb5_error_code ret;
+ krb5_context context = NULL;
+ krb5_principal me = NULL;
+ krb5_principal server = NULL;
+ krb5_principal impersonate_princ = NULL;
+ krb5_creds *creds = NULL;
+ krb5_ccache ccache = NULL;
+
+ *creds_p = NULL;
+
+ initialize_krb5_error_table();
+ ret = krb5_init_context(&context);
+ if (ret) {
+ goto done;
+ }
+
+ if (time_offset != 0) {
+ krb5_set_real_time(context, time(NULL) + time_offset, 0);
+ }
+
+ ret = krb5_cc_resolve(context, cc ? cc :
+ krb5_cc_default_name(context), &ccache);
+ if (ret) {
+ goto done;
+ }
+
+ ret = krb5_cc_get_principal(context, ccache, &me);
+ if (ret) {
+ goto done;
+ }
+
+ ret = smb_krb5_parse_name(context, server_s, &server);
+ if (ret) {
+ goto done;
+ }
+
+ if (impersonate_princ_s) {
+ ret = smb_krb5_parse_name(context, impersonate_princ_s,
+ &impersonate_princ);
+ if (ret) {
+ goto done;
+ }
+ }
+
+ ret = smb_krb5_get_credentials(context, ccache,
+ me, server, impersonate_princ,
+ &creds);
+ if (ret) {
+ goto done;
+ }
+
+ ret = krb5_cc_store_cred(context, ccache, creds);
+ if (ret) {
+ goto done;
+ }
+
+ if (creds_p) {
+ *creds_p = creds;
+ }
+
+ DEBUG(1,("smb_krb5_get_creds: got ticket for %s\n",
+ server_s));
+
+ if (impersonate_princ_s) {
+ char *client = NULL;
+
+ ret = smb_krb5_unparse_name(talloc_tos(), context, creds->client, &client);
+ if (ret) {
+ goto done;
+ }
+ DEBUGADD(1,("smb_krb5_get_creds: using S4U2SELF impersonation as %s\n",
+ client));
+ TALLOC_FREE(client);
+ }
+
+ done:
+ if (!context) {
+ return ret;
+ }
+
+ if (creds && ret) {
+ krb5_free_creds(context, creds);
+ }
+ if (server) {
+ krb5_free_principal(context, server);
+ }
+ if (me) {
+ krb5_free_principal(context, me);
+ }
+ if (impersonate_princ) {
+ krb5_free_principal(context, impersonate_princ);
+ }
+ if (ccache) {
+ krb5_cc_close(context, ccache);
+ }
+ krb5_free_context(context);
+
+ return ret;
+}
+
+/*
+ * smb_krb5_principal_get_realm
+ *
+ * @brief Get realm of a principal
+ *
+ * @param[in] context The krb5_context
+ * @param[in] principal The principal
+ * @return pointer to the realm
+ *
+ */
+
+char *smb_krb5_principal_get_realm(krb5_context context,
+ krb5_principal principal)
+{
+#ifdef HAVE_KRB5_PRINCIPAL_GET_REALM /* Heimdal */
+ return krb5_principal_get_realm(context, principal);
+#elif defined(krb5_princ_realm) /* MIT */
+ krb5_data *realm;
+ realm = krb5_princ_realm(context, principal);
+ return (char *)realm->data;
+#else
+ return NULL;
+#endif
+}
+