#include <stdio.h>
#include <netdb.h>
#include "autoconf.h"
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <string.h>
#include <com_err.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <k5-int.h>
#include <krb5.h>
#ifdef __STDC__
#include <stdlib.h>
#endif
#include <kadm5/admin.h>
#include <kadm5/kadm_rpc.h>
#include "client_internal.h"
#include <gssrpc/rpc.h>
#include <gssapi/gssapi.h>
#include <gssapi/gssapi_krb5.h>
#include <gssrpc/auth_gssapi.h>
#define ADM_CCACHE "/tmp/ovsec_adm.XXXXXX"
enum init_type { INIT_PASS, INIT_SKEY, INIT_CREDS };
static kadm5_ret_t _kadm5_init_any(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
kadm5_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
kadm5_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
kadm5_setup_gss(kadm5_server_handle_t handle,
kadm5_config_params *params_in,
char *client_name, char *full_svcname);
static void
kadm5_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(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 _kadm5_init_any(client_name, INIT_CREDS, NULL, ccache,
service_name, params,
struct_version, api_version, db_args,
server_handle);
}
kadm5_ret_t kadm5_init_with_password(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 _kadm5_init_any(client_name, INIT_PASS, pass, NULL,
service_name, params, struct_version,
api_version, db_args, server_handle);
}
kadm5_ret_t kadm5_init(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 _kadm5_init_any(client_name, INIT_PASS, pass, NULL,
service_name, params, struct_version,
api_version, db_args, server_handle);
}
kadm5_ret_t kadm5_init_with_skey(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 _kadm5_init_any(client_name, INIT_SKEY, keytab, NULL,
service_name, params, struct_version,
api_version, db_args, server_handle);
}
static kadm5_ret_t _kadm5_init_any(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)
{
struct sockaddr_in addr;
struct hostent *hp;
int fd;
char full_svcname[BUFSIZ];
char *realm;
kadm5_server_handle_t handle;
kadm5_config_params params_local;
int code = 0;
generic_ret *r;
initialize_ovk_error_table();
initialize_ovku_error_table();
if (! server_handle) {
return EINVAL;
}
if (! (handle = malloc(sizeof(*handle)))) {
return ENOMEM;
}
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->lhandle = *handle;
handle->lhandle->api_version = KADM5_API_VERSION_2;
handle->lhandle->struct_version = KADM5_STRUCT_VERSION;
handle->lhandle->lhandle = handle->lhandle;
krb5_init_context(&handle->context);
if(client_name == NULL) {
free(handle);
return EINVAL;
}
GENERIC_CHECK_HANDLE(handle, KADM5_OLD_LIB_API_VERSION,
KADM5_NEW_LIB_API_VERSION);
memset((char *) ¶ms_local, 0, sizeof(params_local));
if (api_version == KADM5_API_VERSION_1) {
realm = params_local.realm = (char *) params_in;
if (params_in)
params_local.mask = KADM5_CONFIG_REALM;
params_local.mask |= KADM5_CONFIG_OLD_AUTH_GSSAPI;
params_in = ¶ms_local;
} else {
if (params_in && (params_in->mask & KADM5_CONFIG_REALM))
realm = params_in->realm;
else
realm = NULL;
}
#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;
}
if ((code = kadm5_get_config_params(handle->context, 0,
params_in, &handle->params))) {
krb5_free_context(handle->context);
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) {
krb5_free_context(handle->context);
free(handle);
return KADM5_MISSING_KRB5_CONF_PARAMS;
}
code = kadm5_get_init_creds(handle, client_name, init_type, pass,
ccache_in, service_name, realm,
full_svcname, sizeof(full_svcname));
if (code)
goto error;
hp = gethostbyname(handle->params.admin_server);
if (hp == (struct hostent *) NULL) {
code = KADM5_BAD_SERVER_NAME;
goto cleanup;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = hp->h_addrtype;
(void) memcpy((char *) &addr.sin_addr, (char *) hp->h_addr,
sizeof(addr.sin_addr));
addr.sin_port = htons((u_short) handle->params.kadmind_port);
fd = RPC_ANYSOCK;
handle->clnt = clnttcp_create(&addr, KADM, KADMVERS, &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;
if ((code = _kadm5_check_handle((void *) handle)))
goto error;
code = kadm5_setup_gss(handle, params_in, client_name, full_svcname);
if (code)
goto error;
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;
}
if (r->code) {
code = r->code;
goto error;
}
*server_handle = (void *) handle;
goto cleanup;
error:
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);
cleanup:
if (code)
free(handle);
return code;
}
static kadm5_ret_t
kadm5_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;
krb5_ccache ccache;
char svcname[BUFSIZ];
client = NULL;
ccache = NULL;
if (svcname_in == NULL) {
code = kadm5_get_admin_service_name(handle->context,
handle->params.realm,
svcname, sizeof(svcname));
if (code) {
code = KADM5_MISSING_KRB5_CONF_PARAMS;
goto error;
}
} else {
strncpy(svcname, svcname_in, sizeof(svcname));
svcname[sizeof(svcname)-1] = '\0';
}
code = krb5_parse_name(handle->context, client_name, &client);
if (code)
goto error;
if (init_type == INIT_CREDS) {
ccache = ccache_in;
handle->cache_name = (char *)
malloc(strlen(krb5_cc_get_type(handle->context, ccache)) +
strlen(krb5_cc_get_name(handle->context, ccache)) + 2);
if (handle->cache_name == NULL) {
code = ENOMEM;
goto error;
}
sprintf(handle->cache_name, "%s:%s",
krb5_cc_get_type(handle->context, ccache),
krb5_cc_get_name(handle->context, ccache));
} else {
static int counter = 0;
handle->cache_name = malloc(sizeof("MEMORY:kadm5_")
+ 3*sizeof(counter));
sprintf(handle->cache_name, "MEMORY:kadm5_%u", counter++);
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 = kadm5_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) {
code = kadm5_gic_iter(handle, init_type, ccache,
client, pass,
KADM5_ADMIN_SERVICE, realm,
full_svcname, full_svcname_len);
}
if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY) code = KADM5_BAD_PASSWORD;
if (code == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN)
code = KADM5_SECURE_PRINC_MISSING;
error:
if (ccache != NULL && init_type != INIT_CREDS)
krb5_cc_close(handle->context, ccache);
return code;
}
static kadm5_ret_t
kadm5_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;
krb5_creds mcreds, outcreds;
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) {
if ((strlen(svcname) + strlen(realm) + 1) >= full_svcname_len)
goto error;
sprintf(full_svcname, "%s@%s", svcname, realm);
} else {
if ((strlen(svcname) + krb5_princ_realm(ctx, client)->length + 1)
>= full_svcname_len)
goto error;
strcpy(full_svcname, svcname);
strcat(full_svcname, "@");
strncat(full_svcname,
krb5_princ_realm(ctx, client)->data,
krb5_princ_realm(ctx, client)->length);
}
if (init_type != INIT_CREDS)
krb5_get_init_creds_opt_init(&opt);
if (init_type == INIT_PASS) {
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;
}
if (init_type != INIT_CREDS) {
code = krb5_cc_store_cred(ctx, ccache, &outcreds);
if (code)
goto error;
}
error:
krb5_free_cred_contents(ctx, &outcreds);
return code;
}
static kadm5_ret_t
kadm5_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;
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;
}
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);
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;
goto error;
}
kadm5_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);
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;
}
static void
kadm5_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;
if (params_in != NULL && (params_in->mask & KADM5_CONFIG_NO_AUTH))
return;
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;
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);
krb5_free_context(handle->context);
handle->magic_number = 0;
free(handle);
return code;
}
kadm5_ret_t kadm5_lock(void *server_handle)
{
return EINVAL;
}
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);
}