/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved */ /* * Copyright (C) 1998 by the FundsXpress, INC. * * 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 FundsXpress. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. FundsXpress makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include "autoconf.h" #ifdef HAVE_MEMORY_H #include #endif #include #include #include #include #include #include #include /* for KRB5_ADM_DEFAULT_PORT */ #include #ifdef __STDC__ #include #endif #include #include #include "client_internal.h" #include #include "iprop.h" #include #include #include #include #define ADM_CCACHE "/tmp/ovsec_adm.XXXXXX" enum init_type { INIT_PASS, INIT_SKEY, INIT_CREDS, INIT_ANONYMOUS }; static kadm5_ret_t init_any(krb5_context context, char *client_name, enum init_type init_type, char *pass, krb5_ccache ccache_in, char *service_name, kadm5_config_params *params, krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args, void **server_handle); static kadm5_ret_t get_init_creds(kadm5_server_handle_t handle, char *client_name, enum init_type init_type, char *pass, krb5_ccache ccache_in, char *svcname_in, char *realm, char *full_svcname, unsigned int full_svcname_len); static kadm5_ret_t gic_iter(kadm5_server_handle_t handle, enum init_type init_type, krb5_ccache ccache, krb5_principal client, char *pass, char *svcname, char *realm, char *full_svcname, unsigned int full_svcname_len); static kadm5_ret_t connect_to_server(const char *hostname, int port, int *fd); static kadm5_ret_t setup_gss(kadm5_server_handle_t handle, kadm5_config_params *params_in, char *client_name, char *full_svcname); static void rpc_auth(kadm5_server_handle_t handle, kadm5_config_params *params_in, gss_cred_id_t gss_client_creds, gss_name_t gss_target); kadm5_ret_t kadm5_init_with_creds(krb5_context context, char *client_name, krb5_ccache ccache, char *service_name, kadm5_config_params *params, krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args, void **server_handle) { return init_any(context, client_name, INIT_CREDS, NULL, ccache, service_name, params, struct_version, api_version, db_args, server_handle); } kadm5_ret_t kadm5_init_with_password(krb5_context context, char *client_name, char *pass, char *service_name, kadm5_config_params *params, krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args, void **server_handle) { return init_any(context, client_name, INIT_PASS, pass, NULL, service_name, params, struct_version, api_version, db_args, server_handle); } kadm5_ret_t kadm5_init_anonymous(krb5_context context, char *client_name, char *service_name, kadm5_config_params *params, krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args, void **server_handle) { return init_any(context, client_name, INIT_ANONYMOUS, NULL, NULL, service_name, params, struct_version, api_version, db_args, server_handle); } kadm5_ret_t kadm5_init(krb5_context context, char *client_name, char *pass, char *service_name, kadm5_config_params *params, krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args, void **server_handle) { return init_any(context, client_name, INIT_PASS, pass, NULL, service_name, params, struct_version, api_version, db_args, server_handle); } kadm5_ret_t kadm5_init_with_skey(krb5_context context, char *client_name, char *keytab, char *service_name, kadm5_config_params *params, krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args, void **server_handle) { return init_any(context, client_name, INIT_SKEY, keytab, NULL, service_name, params, struct_version, api_version, db_args, server_handle); } static kadm5_ret_t init_any(krb5_context context, char *client_name, enum init_type init_type, char *pass, krb5_ccache ccache_in, char *service_name, kadm5_config_params *params_in, krb5_ui_4 struct_version, krb5_ui_4 api_version, char **db_args, void **server_handle) { int fd; krb5_boolean iprop_enable; int port; rpcprog_t rpc_prog; rpcvers_t rpc_vers; char full_svcname[BUFSIZ]; char *realm; krb5_ccache ccache; kadm5_server_handle_t handle; kadm5_config_params params_local; int code = 0; generic_ret *r; initialize_ovk_error_table(); /* initialize_adb_error_table(); */ initialize_ovku_error_table(); if (! server_handle) { return EINVAL; } if (! (handle = malloc(sizeof(*handle)))) { return ENOMEM; } memset(handle, 0, sizeof(*handle)); if (! (handle->lhandle = malloc(sizeof(*handle)))) { free(handle); return ENOMEM; } handle->magic_number = KADM5_SERVER_HANDLE_MAGIC; handle->struct_version = struct_version; handle->api_version = api_version; handle->clnt = 0; handle->cache_name = 0; handle->destroy_cache = 0; handle->context = 0; *handle->lhandle = *handle; handle->lhandle->api_version = KADM5_API_VERSION_3; handle->lhandle->struct_version = KADM5_STRUCT_VERSION; handle->lhandle->lhandle = handle->lhandle; handle->context = context; if(client_name == NULL) { free(handle); return EINVAL; } /* * Verify the version numbers before proceeding; we can't use * CHECK_HANDLE because not all fields are set yet. */ GENERIC_CHECK_HANDLE(handle, KADM5_OLD_LIB_API_VERSION, KADM5_NEW_LIB_API_VERSION); /* * Acquire relevant profile entries. In version 2, merge values * in params_in with values from profile, based on * params_in->mask. * * In version 1, we've given a realm (which may be NULL) instead * of params_in. So use that realm, make params_in contain an * empty mask, and behave like version 2. */ memset(¶ms_local, 0, sizeof(params_local)); if (params_in && (params_in->mask & KADM5_CONFIG_REALM)) realm = params_in->realm; else realm = NULL; #if 0 /* Since KDC config params can now be put in krb5.conf, these could show up even when you're just using the remote kadmin client. */ #define ILLEGAL_PARAMS (KADM5_CONFIG_DBNAME | KADM5_CONFIG_ADBNAME | \ KADM5_CONFIG_ADB_LOCKFILE | \ KADM5_CONFIG_ACL_FILE | KADM5_CONFIG_DICT_FILE \ | KADM5_CONFIG_ADMIN_KEYTAB | \ KADM5_CONFIG_STASH_FILE | \ KADM5_CONFIG_MKEY_NAME | KADM5_CONFIG_ENCTYPE \ | KADM5_CONFIG_MAX_LIFE | \ KADM5_CONFIG_MAX_RLIFE | \ KADM5_CONFIG_EXPIRATION | KADM5_CONFIG_FLAGS | \ KADM5_CONFIG_ENCTYPES | KADM5_CONFIG_MKEY_FROM_KBD) if (params_in && params_in->mask & ILLEGAL_PARAMS) { free(handle); return KADM5_BAD_CLIENT_PARAMS; } #endif if ((code = kadm5_get_config_params(handle->context, 0, params_in, &handle->params))) { free(handle); return(code); } #define REQUIRED_PARAMS (KADM5_CONFIG_REALM | \ KADM5_CONFIG_ADMIN_SERVER | \ KADM5_CONFIG_KADMIND_PORT) if ((handle->params.mask & REQUIRED_PARAMS) != REQUIRED_PARAMS) { free(handle); return KADM5_MISSING_KRB5_CONF_PARAMS; } /* * Get credentials. Also does some fallbacks in case kadmin/fqdn * principal doesn't exist. */ code = get_init_creds(handle, client_name, init_type, pass, ccache_in, service_name, realm, full_svcname, sizeof(full_svcname)); if (code) goto error; /* If the service_name and client_name are iprop-centric, use the iprop * port and RPC identifiers. */ iprop_enable = (service_name != NULL && strstr(service_name, KIPROP_SVC_NAME) != NULL && strstr(client_name, KIPROP_SVC_NAME) != NULL); if (iprop_enable) { port = handle->params.iprop_port; rpc_prog = KRB5_IPROP_PROG; rpc_vers = KRB5_IPROP_VERS; } else { port = handle->params.kadmind_port; rpc_prog = KADM; rpc_vers = KADMVERS; } code = connect_to_server(handle->params.admin_server, port, &fd); if (code) goto error; handle->clnt = clnttcp_create(NULL, rpc_prog, rpc_vers, &fd, 0, 0); if (handle->clnt == NULL) { code = KADM5_RPC_ERROR; #ifdef DEBUG clnt_pcreateerror("clnttcp_create"); #endif goto error; } handle->lhandle->clnt = handle->clnt; /* now that handle->clnt is set, we can check the handle */ if ((code = _kadm5_check_handle((void *) handle))) goto error; /* * The RPC connection is open; establish the GSS-API * authentication context. */ code = setup_gss(handle, params_in, (init_type == INIT_CREDS) ? client_name : NULL, full_svcname); if (code) goto error; /* * Bypass the remainder of the code and return straightaway * if the gss service requested is kiprop */ if (iprop_enable) { code = 0; *server_handle = (void *) handle; goto cleanup; } r = init_2(&handle->api_version, handle->clnt); if (r == NULL) { code = KADM5_RPC_ERROR; #ifdef DEBUG clnt_perror(handle->clnt, "init_2 null resp"); #endif goto error; } /* Drop down to v2 wire protocol if server does not support v3 */ if (r->code == KADM5_NEW_SERVER_API_VERSION && handle->api_version == KADM5_API_VERSION_3) { handle->api_version = KADM5_API_VERSION_2; r = init_2(&handle->api_version, handle->clnt); if (r == NULL) { code = KADM5_RPC_ERROR; goto error; } } if (r->code) { code = r->code; goto error; } *server_handle = (void *) handle; goto cleanup; error: /* * Note that it is illegal for this code to execute if "handle" * has not been allocated and initialized. I.e., don't use "goto * error" before the block of code at the top of the function * that allocates and initializes "handle". */ if (handle->destroy_cache && handle->cache_name) { if (krb5_cc_resolve(handle->context, handle->cache_name, &ccache) == 0) (void) krb5_cc_destroy (handle->context, ccache); } if (handle->cache_name) free(handle->cache_name); if(handle->clnt && handle->clnt->cl_auth) AUTH_DESTROY(handle->clnt->cl_auth); if(handle->clnt) clnt_destroy(handle->clnt); kadm5_free_config_params(handle->context, &handle->params); cleanup: if (code) free(handle); return code; } /* Get initial credentials for authenticating to server. Perform fallback from * kadmin/fqdn to kadmin/admin if svcname_in is NULL. */ static kadm5_ret_t get_init_creds(kadm5_server_handle_t handle, char *client_name, enum init_type init_type, char *pass, krb5_ccache ccache_in, char *svcname_in, char *realm, char *full_svcname, unsigned int full_svcname_len) { kadm5_ret_t code; krb5_principal client = NULL; krb5_ccache ccache = NULL; char svcname[BUFSIZ]; /* NULL svcname means use host-based. */ if (svcname_in == NULL) { code = kadm5_get_admin_service_name(handle->context, handle->params.realm, svcname, sizeof(svcname)); if (code) goto error; } else { strncpy(svcname, svcname_in, sizeof(svcname)); svcname[sizeof(svcname)-1] = '\0'; } /* * Acquire a service ticket for svcname@realm in the name of * client_name, using password pass (which could be NULL), and * create a ccache to store them in. If INIT_CREDS, use the * ccache we were provided instead. */ code = krb5_parse_name(handle->context, client_name, &client); if (code) goto error; if (init_type == INIT_CREDS) { ccache = ccache_in; if (asprintf(&handle->cache_name, "%s:%s", krb5_cc_get_type(handle->context, ccache), krb5_cc_get_name(handle->context, ccache)) < 0) { handle->cache_name = NULL; code = ENOMEM; goto error; } } else { static int counter = 0; if (asprintf(&handle->cache_name, "MEMORY:kadm5_%u", counter++) < 0) { handle->cache_name = NULL; code = ENOMEM; goto error; } code = krb5_cc_resolve(handle->context, handle->cache_name, &ccache); if (code) goto error; code = krb5_cc_initialize (handle->context, ccache, client); if (code) goto error; handle->destroy_cache = 1; } handle->lhandle->cache_name = handle->cache_name; code = gic_iter(handle, init_type, ccache, client, pass, svcname, realm, full_svcname, full_svcname_len); if ((code == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN || code == KRB5_CC_NOTFOUND) && svcname_in == NULL) { /* Retry with old host-independent service principal. */ code = gic_iter(handle, init_type, ccache, client, pass, KADM5_ADMIN_SERVICE, realm, full_svcname, full_svcname_len); } /* Improved error messages */ if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY) code = KADM5_BAD_PASSWORD; if (code == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) code = KADM5_SECURE_PRINC_MISSING; error: krb5_free_principal(handle->context, client); if (ccache != NULL && init_type != INIT_CREDS) krb5_cc_close(handle->context, ccache); return code; } /* Perform one iteration of attempting to get credentials. This includes * searching existing ccache for requested service if INIT_CREDS. */ static kadm5_ret_t gic_iter(kadm5_server_handle_t handle, enum init_type init_type, krb5_ccache ccache, krb5_principal client, char *pass, char *svcname, char *realm, char *full_svcname, unsigned int full_svcname_len) { kadm5_ret_t code; krb5_context ctx; krb5_keytab kt; krb5_get_init_creds_opt *opt = NULL; krb5_creds mcreds, outcreds; int n; ctx = handle->context; kt = NULL; memset(full_svcname, 0, full_svcname_len); memset(&opt, 0, sizeof(opt)); memset(&mcreds, 0, sizeof(mcreds)); memset(&outcreds, 0, sizeof(outcreds)); code = ENOMEM; if (realm) { n = snprintf(full_svcname, full_svcname_len, "%s@%s", svcname, realm); if (n < 0 || n >= (int) full_svcname_len) goto error; } else { /* krb5_princ_realm(client) is not null terminated */ n = snprintf(full_svcname, full_svcname_len, "%s@%.*s", svcname, krb5_princ_realm(ctx, client)->length, krb5_princ_realm(ctx, client)->data); if (n < 0 || n >= (int) full_svcname_len) goto error; } /* Credentials for kadmin don't need to be forwardable or proxiable. */ if (init_type != INIT_CREDS) { code = krb5_get_init_creds_opt_alloc(ctx, &opt); krb5_get_init_creds_opt_set_forwardable(opt, 0); krb5_get_init_creds_opt_set_proxiable(opt, 0); krb5_get_init_creds_opt_set_out_ccache(ctx, opt, ccache); if (init_type == INIT_ANONYMOUS) krb5_get_init_creds_opt_set_anonymous(opt, 1); } if (init_type == INIT_PASS || init_type == INIT_ANONYMOUS) { code = krb5_get_init_creds_password(ctx, &outcreds, client, pass, krb5_prompter_posix, NULL, 0, full_svcname, opt); if (code) goto error; } else if (init_type == INIT_SKEY) { if (pass) { code = krb5_kt_resolve(ctx, pass, &kt); if (code) goto error; } code = krb5_get_init_creds_keytab(ctx, &outcreds, client, kt, 0, full_svcname, opt); if (pass) krb5_kt_close(ctx, kt); if (code) goto error; } else if (init_type == INIT_CREDS) { mcreds.client = client; code = krb5_parse_name(ctx, full_svcname, &mcreds.server); if (code) goto error; code = krb5_cc_retrieve_cred(ctx, ccache, 0, &mcreds, &outcreds); krb5_free_principal(ctx, mcreds.server); if (code) goto error; } error: krb5_free_cred_contents(ctx, &outcreds); if (opt) krb5_get_init_creds_opt_free(ctx, opt); return code; } /* Set *fd to a socket connected to hostname and port. */ static kadm5_ret_t connect_to_server(const char *hostname, int port, int *fd) { struct addrinfo hint, *addrs, *a; char portbuf[32]; int err, s; kadm5_ret_t code; /* Look up the server's addresses. */ (void) snprintf(portbuf, sizeof(portbuf), "%d", port); memset(&hint, 0, sizeof(hint)); hint.ai_socktype = SOCK_STREAM; #ifdef AI_NUMERICSERV hint.ai_flags = AI_NUMERICSERV; #endif err = getaddrinfo(hostname, portbuf, &hint, &addrs); if (err != 0) return KADM5_CANT_RESOLVE; /* Try to connect to each address until we succeed. */ for (a = addrs; a != NULL; a = a->ai_next) { s = socket(a->ai_family, a->ai_socktype, 0); if (s == -1) { code = KADM5_FAILURE; goto cleanup; } err = connect(s, a->ai_addr, a->ai_addrlen); if (err == 0) { *fd = s; code = 0; goto cleanup; } close(s); } /* We didn't succeed on any address. */ code = KADM5_RPC_ERROR; cleanup: freeaddrinfo(addrs); return code; } /* Acquire GSSAPI credentials and set up RPC auth flavor. */ static kadm5_ret_t setup_gss(kadm5_server_handle_t handle, kadm5_config_params *params_in, char *client_name, char *full_svcname) { kadm5_ret_t code; OM_uint32 gssstat, minor_stat; gss_buffer_desc buf; gss_name_t gss_client; gss_name_t gss_target; gss_cred_id_t gss_client_creds; const char *c_ccname_orig; char *ccname_orig; code = KADM5_GSS_ERROR; gss_client_creds = GSS_C_NO_CREDENTIAL; ccname_orig = NULL; gss_client = gss_target = GSS_C_NO_NAME; /* Temporarily use the kadm5 cache. */ gssstat = gss_krb5_ccache_name(&minor_stat, handle->cache_name, &c_ccname_orig); if (gssstat != GSS_S_COMPLETE) { code = KADM5_GSS_ERROR; goto error; } if (c_ccname_orig) ccname_orig = strdup(c_ccname_orig); else ccname_orig = 0; buf.value = full_svcname; buf.length = strlen((char *)buf.value) + 1; gssstat = gss_import_name(&minor_stat, &buf, (gss_OID) gss_nt_krb5_name, &gss_target); if (gssstat != GSS_S_COMPLETE) { code = KADM5_GSS_ERROR; goto error; } if (client_name) { buf.value = client_name; buf.length = strlen((char *)buf.value) + 1; gssstat = gss_import_name(&minor_stat, &buf, (gss_OID) gss_nt_krb5_name, &gss_client); } else gss_client = GSS_C_NO_NAME; if (gssstat != GSS_S_COMPLETE) { code = KADM5_GSS_ERROR; goto error; } gssstat = gss_acquire_cred(&minor_stat, gss_client, 0, GSS_C_NULL_OID_SET, GSS_C_INITIATE, &gss_client_creds, NULL, NULL); if (gssstat != GSS_S_COMPLETE) { code = KADM5_GSS_ERROR; #if 0 /* for debugging only */ { OM_uint32 maj_status, min_status, message_context = 0; gss_buffer_desc status_string; do { maj_status = gss_display_status(&min_status, gssstat, GSS_C_GSS_CODE, GSS_C_NO_OID, &message_context, &status_string); if (maj_status == GSS_S_COMPLETE) { fprintf(stderr, "MAJ: %.*s\n", (int) status_string.length, (char *)status_string.value); gss_release_buffer(&min_status, &status_string); } else { fprintf(stderr, "MAJ? gss_display_status returns 0x%lx?!\n", (unsigned long) maj_status); message_context = 0; } } while (message_context != 0); do { maj_status = gss_display_status(&min_status, minor_stat, GSS_C_MECH_CODE, GSS_C_NO_OID, &message_context, &status_string); if (maj_status == GSS_S_COMPLETE) { fprintf(stderr, "MIN: %.*s\n", (int) status_string.length, (char *)status_string.value); gss_release_buffer(&min_status, &status_string); } else { fprintf(stderr, "MIN? gss_display_status returns 0x%lx?!\n", (unsigned long) maj_status); message_context = 0; } } while (message_context != 0); } #endif goto error; } /* * Do actual creation of RPC auth handle. Implements auth flavor * fallback. */ rpc_auth(handle, params_in, gss_client_creds, gss_target); error: if (gss_client_creds != GSS_C_NO_CREDENTIAL) (void) gss_release_cred(&minor_stat, &gss_client_creds); if (gss_client) gss_release_name(&minor_stat, &gss_client); if (gss_target) gss_release_name(&minor_stat, &gss_target); /* Revert to prior gss_krb5 ccache. */ if (ccname_orig) { gssstat = gss_krb5_ccache_name(&minor_stat, ccname_orig, NULL); if (gssstat) { return KADM5_GSS_ERROR; } free(ccname_orig); } else { gssstat = gss_krb5_ccache_name(&minor_stat, NULL, NULL); if (gssstat) { return KADM5_GSS_ERROR; } } if (handle->clnt->cl_auth == NULL) { return KADM5_GSS_ERROR; } return 0; } /* Create RPC auth handle. Do auth flavor fallback if needed. */ static void rpc_auth(kadm5_server_handle_t handle, kadm5_config_params *params_in, gss_cred_id_t gss_client_creds, gss_name_t gss_target) { OM_uint32 gssstat, minor_stat; struct rpc_gss_sec sec; /* Allow unauthenticated option for testing. */ if (params_in != NULL && (params_in->mask & KADM5_CONFIG_NO_AUTH)) return; /* Use RPCSEC_GSS by default. */ if (params_in == NULL || !(params_in->mask & KADM5_CONFIG_OLD_AUTH_GSSAPI)) { sec.mech = gss_mech_krb5; sec.qop = GSS_C_QOP_DEFAULT; sec.svc = RPCSEC_GSS_SVC_PRIVACY; sec.cred = gss_client_creds; sec.req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG; handle->clnt->cl_auth = authgss_create(handle->clnt, gss_target, &sec); if (handle->clnt->cl_auth != NULL) return; } if (params_in != NULL && (params_in->mask & KADM5_CONFIG_AUTH_NOFALLBACK)) return; /* Fall back to old AUTH_GSSAPI. */ handle->clnt->cl_auth = auth_gssapi_create(handle->clnt, &gssstat, &minor_stat, gss_client_creds, gss_target, (gss_OID) gss_mech_krb5, GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, 0, NULL, NULL, NULL); } kadm5_ret_t kadm5_destroy(void *server_handle) { krb5_ccache ccache = NULL; int code = KADM5_OK; kadm5_server_handle_t handle = (kadm5_server_handle_t) server_handle; CHECK_HANDLE(server_handle); if (handle->destroy_cache && handle->cache_name) { if ((code = krb5_cc_resolve(handle->context, handle->cache_name, &ccache)) == 0) code = krb5_cc_destroy (handle->context, ccache); } if (handle->cache_name) free(handle->cache_name); if (handle->clnt && handle->clnt->cl_auth) AUTH_DESTROY(handle->clnt->cl_auth); if (handle->clnt) clnt_destroy(handle->clnt); if (handle->lhandle) free (handle->lhandle); kadm5_free_config_params(handle->context, &handle->params); handle->magic_number = 0; free(handle); return code; } /* not supported on client */ kadm5_ret_t kadm5_lock(void *server_handle) { return EINVAL; } /* not supported on client */ kadm5_ret_t kadm5_unlock(void *server_handle) { return EINVAL; } kadm5_ret_t kadm5_flush(void *server_handle) { return KADM5_OK; } int _kadm5_check_handle(void *handle) { CHECK_HANDLE(handle); return 0; } krb5_error_code kadm5_init_krb5_context (krb5_context *ctx) { return krb5_init_context(ctx); } /* * Stub function for kadmin. It was created to eliminate the dependency on * libkdb's ulog functions. The srv equivalent makes the actual calls. */ krb5_error_code kadm5_init_iprop(void *handle, char **db_args) { return (0); }