1/*
2 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
3 * Use is subject to license terms.
4 */
5
6
7
8/*
9 * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved
10 */
11
12/*
13 * Copyright (C) 1998 by the FundsXpress, INC.
14 *
15 * All rights reserved.
16 *
17 * Export of this software from the United States of America may require
18 * a specific license from the United States Government.  It is the
19 * responsibility of any person or organization contemplating export to
20 * obtain such a license before exporting.
21 *
22 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
23 * distribute this software and its documentation for any purpose and
24 * without fee is hereby granted, provided that the above copyright
25 * notice appear in all copies and that both that copyright notice and
26 * this permission notice appear in supporting documentation, and that
27 * the name of FundsXpress. not be used in advertising or publicity pertaining
28 * to distribution of the software without specific, written prior
29 * permission.  FundsXpress makes no representations about the suitability of
30 * this software for any purpose.  It is provided "as is" without express
31 * or implied warranty.
32 *
33 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
34 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
35 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
36 */
37
38#include <stdio.h>
39#include <netdb.h>
40#include "autoconf.h"
41#ifdef HAVE_MEMORY_H
42#include <memory.h>
43#endif
44#include <string.h>
45#include <com_err.h>
46#include <sys/types.h>
47#include <sys/socket.h>
48#include <netinet/in.h>
49#include <k5-int.h> /* for KRB5_ADM_DEFAULT_PORT */
50#include <krb5.h>
51#ifdef __STDC__
52#include <stdlib.h>
53#endif
54#include <libintl.h>
55
56#include <kadm5/admin.h>
57#include <kadm5/kadm_rpc.h>
58#include "client_internal.h"
59
60#include <syslog.h>
61#include <gssapi/gssapi.h>
62#include <gssapi_krb5.h>
63#include <gssapiP_krb5.h>
64#include <rpc/clnt.h>
65
66#include <iprop_hdr.h>
67#include "iprop.h"
68
69#define	ADM_CCACHE  "/tmp/ovsec_adm.XXXXXX"
70
71static int old_auth_gssapi = 0;
72/* connection timeout to kadmind in seconds */
73#define		KADMIND_CONNECT_TIMEOUT	25
74
75int _kadm5_check_handle();
76
77enum init_type { INIT_PASS, INIT_SKEY, INIT_CREDS };
78
79static kadm5_ret_t _kadm5_init_any(char *client_name,
80				   enum init_type init_type,
81				   char *pass,
82				   krb5_ccache ccache_in,
83				   char *service_name,
84				   kadm5_config_params *params,
85				   krb5_ui_4 struct_version,
86				   krb5_ui_4 api_version,
87				   char **db_args,
88				   void **server_handle);
89
90kadm5_ret_t kadm5_init_with_creds(char *client_name,
91				  krb5_ccache ccache,
92				  char *service_name,
93				  kadm5_config_params *params,
94				  krb5_ui_4 struct_version,
95				  krb5_ui_4 api_version,
96				  char **db_args,
97				  void **server_handle)
98{
99     return _kadm5_init_any(client_name, INIT_CREDS, NULL, ccache,
100			    service_name, params,
101			    struct_version, api_version, db_args,
102			    server_handle);
103}
104
105
106kadm5_ret_t kadm5_init_with_password(char *client_name, char *pass,
107				     char *service_name,
108				     kadm5_config_params *params,
109				     krb5_ui_4 struct_version,
110				     krb5_ui_4 api_version,
111				     char **db_args,
112				     void **server_handle)
113{
114     return _kadm5_init_any(client_name, INIT_PASS, pass, NULL,
115			    service_name, params, struct_version,
116			    api_version, db_args, server_handle);
117}
118
119kadm5_ret_t kadm5_init(char *client_name, char *pass,
120		       char *service_name,
121		       kadm5_config_params *params,
122		       krb5_ui_4 struct_version,
123		       krb5_ui_4 api_version,
124		       char **db_args,
125		       void **server_handle)
126{
127     return _kadm5_init_any(client_name, INIT_PASS, pass, NULL,
128			    service_name, params, struct_version,
129			    api_version, db_args, server_handle);
130}
131
132kadm5_ret_t kadm5_init_with_skey(char *client_name, char *keytab,
133				 char *service_name,
134				 kadm5_config_params *params,
135				 krb5_ui_4 struct_version,
136				 krb5_ui_4 api_version,
137				 char **db_args,
138				 void **server_handle)
139{
140     return _kadm5_init_any(client_name, INIT_SKEY, keytab, NULL,
141			    service_name, params, struct_version,
142			    api_version, db_args, server_handle);
143}
144
145krb5_error_code  kadm5_free_config_params();
146
147static void
148display_status_1(m, code, type, mech)
149char *m;
150OM_uint32 code;
151int type;
152const gss_OID mech;
153{
154	OM_uint32 maj_stat, min_stat;
155	gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
156	OM_uint32 msg_ctx;
157
158	msg_ctx = 0;
159	ADMIN_LOG(LOG_ERR, "%s\n", m);
160	/* LINTED */
161	while (1) {
162		maj_stat = gss_display_status(&min_stat, code,
163					    type, mech,
164					    &msg_ctx, &msg);
165		if (maj_stat != GSS_S_COMPLETE) {
166			syslog(LOG_ERR,
167			    dgettext(TEXT_DOMAIN,
168				    "error in gss_display_status"
169				    " called from <%s>\n"), m);
170			break;
171		} else
172			syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
173						"GSS-API error : %s\n"),
174			    m);
175		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
176					"GSS-API error : %s\n"),
177		    (char *)msg.value);
178		if (msg.length != 0)
179			(void) gss_release_buffer(&min_stat, &msg);
180
181		if (!msg_ctx)
182			break;
183	}
184}
185
186/*
187 * Function: display_status
188 *
189 * Purpose: displays GSS-API messages
190 *
191 * Arguments:
192 *
193 * 	msg		a string to be displayed with the message
194 * 	maj_stat	the GSS-API major status code
195 * 	min_stat	the GSS-API minor status code
196 *	mech		kerberos mech
197 * Effects:
198 *
199 * The GSS-API messages associated with maj_stat and min_stat are
200 * displayed on stderr, each preceeded by "GSS-API error <msg>: " and
201 * followed by a newline.
202 */
203void
204display_status(msg, maj_stat, min_stat, mech)
205char *msg;
206OM_uint32 maj_stat;
207OM_uint32 min_stat;
208char *mech;
209{
210	gss_OID mech_oid;
211
212	if (!rpc_gss_mech_to_oid(mech, (rpc_gss_OID *)&mech_oid)) {
213		ADMIN_LOG(LOG_ERR,
214			dgettext(TEXT_DOMAIN,
215				"Invalid mechanism oid <%s>"), mech);
216		return;
217	}
218
219	display_status_1(msg, maj_stat, GSS_C_GSS_CODE, mech_oid);
220	display_status_1(msg, min_stat, GSS_C_MECH_CODE, mech_oid);
221}
222
223/*
224 * Open an fd for the given address and connect asynchronously. Wait
225 * KADMIND_CONNECT_TIMEOUT seconds or till it succeeds. If it succeeds
226 * change fd to blocking and return it, else return -1.
227 */
228static int
229get_connection(struct netconfig *nconf, struct netbuf netaddr)
230{
231	struct t_info tinfo;
232	struct t_call sndcall;
233	struct t_call *rcvcall = NULL;
234	int connect_time;
235	int flags;
236	int fd;
237
238	(void) memset(&tinfo, 0, sizeof (tinfo));
239
240	/* we'l open with O_NONBLOCK and avoid an fcntl */
241	fd = t_open(nconf->nc_device, O_RDWR | O_NONBLOCK, &tinfo);
242	if (fd == -1) {
243		return (-1);
244	}
245
246	if (t_bind(fd, (struct t_bind *)NULL, (struct t_bind *)NULL) == -1) {
247		(void) close(fd);
248		return (-1);
249	}
250
251	/* we can't connect unless fd is in IDLE state */
252	if (t_getstate(fd) != T_IDLE) {
253		(void) close(fd);
254		return (-1);
255	}
256
257	/* setup connect parameters */
258	netaddr.len = netaddr.maxlen = __rpc_get_a_size(tinfo.addr);
259	sndcall.addr = netaddr;
260	sndcall.opt.len = sndcall.udata.len = 0;
261
262	/* we wait for KADMIND_CONNECT_TIMEOUT seconds from now */
263	connect_time = time(NULL) + KADMIND_CONNECT_TIMEOUT;
264	if (t_connect(fd, &sndcall, rcvcall) != 0) {
265		if (t_errno != TNODATA) {
266			(void) close(fd);
267			return (-1);
268		}
269	}
270
271	/* loop till success or timeout */
272	for (;;) {
273		if (t_rcvconnect(fd, rcvcall) == 0)
274			break;
275
276		if (t_errno != TNODATA || time(NULL) > connect_time) {
277			/* we have either timed out or caught an error */
278			(void) close(fd);
279			if (rcvcall != NULL)
280				t_free((char *)rcvcall, T_CALL);
281			return (-1);
282		}
283		sleep(1);
284	}
285
286	/* make the fd blocking (synchronous) */
287	flags = fcntl(fd, F_GETFL, 0);
288	(void) fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
289	if (rcvcall != NULL)
290		t_free((char *)rcvcall, T_CALL);
291	return (fd);
292}
293
294/*
295 * Open an RPCSEC_GSS connection and
296 * get a client handle to use for future RPCSEC calls.
297 *
298 * This function is only used when changing passwords and
299 * the kpasswd_protocol is RPCSEC_GSS
300 */
301static int
302_kadm5_initialize_rpcsec_gss_handle(kadm5_server_handle_t handle,
303				    char *client_name,
304				    char *service_name)
305{
306	struct netbuf netaddr;
307	struct hostent *hp;
308	int fd;
309	struct sockaddr_in addr;
310	struct sockaddr_in *sin;
311	struct netconfig *nconf;
312	int code = 0;
313	generic_ret *r;
314	char *ccname_orig;
315	char *iprop_svc;
316	boolean_t iprop_enable = B_FALSE;
317	char mech[] = "kerberos_v5";
318	gss_OID mech_oid;
319	gss_OID_set_desc oid_set;
320	gss_name_t gss_client;
321	gss_buffer_desc input_name;
322	gss_cred_id_t gss_client_creds = GSS_C_NO_CREDENTIAL;
323	rpc_gss_options_req_t   options_req;
324	rpc_gss_options_ret_t   options_ret;
325	rpc_gss_service_t service = rpc_gss_svc_privacy;
326	OM_uint32 gssstat, minor_stat;
327	void *handlep;
328	enum clnt_stat rpc_err_code;
329	char *server = handle->params.admin_server;
330
331	/*
332	 * Try to find the kpasswd_server first if this is for the changepw
333	 * service.  If defined then it should be resolvable else return error.
334	 */
335	if (strncmp(service_name, KADM5_CHANGEPW_HOST_SERVICE,
336	    strlen(KADM5_CHANGEPW_HOST_SERVICE)) == 0) {
337		if (handle->params.kpasswd_server != NULL)
338			server = handle->params.kpasswd_server;
339	}
340	hp = gethostbyname(server);
341	if (hp == (struct hostent *)NULL) {
342		code = KADM5_BAD_SERVER_NAME;
343		ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN,
344					    "bad server name\n"));
345		goto cleanup;
346	}
347
348	memset(&addr, 0, sizeof (addr));
349	addr.sin_family = hp->h_addrtype;
350	(void) memcpy((char *)&addr.sin_addr, (char *)hp->h_addr,
351		    sizeof (addr.sin_addr));
352	addr.sin_port = htons((ushort_t)handle->params.kadmind_port);
353	sin = &addr;
354#ifdef DEBUG
355	printf("kadmin_port %d\n", handle->params.kadmind_port);
356	printf("addr: sin_port: %d, sin_family: %d, sin_zero %s\n",
357	    addr.sin_port, addr.sin_family, addr.sin_zero);
358	printf("sin_addr %d:%d\n", addr.sin_addr.S_un.S_un_w.s_w1,
359	    addr.sin_addr.S_un.S_un_w.s_w2);
360#endif
361	if ((handlep = setnetconfig()) == (void *) NULL) {
362		(void) syslog(LOG_ERR,
363			    dgettext(TEXT_DOMAIN,
364				    "cannot get any transport information"));
365		goto error;
366	}
367
368	while (nconf = getnetconfig(handlep)) {
369		if ((nconf->nc_semantics == NC_TPI_COTS_ORD) &&
370		    (strcmp(nconf->nc_protofmly, NC_INET) == 0) &&
371		    (strcmp(nconf->nc_proto, NC_TCP) == 0))
372			break;
373	}
374
375	if (nconf == (struct netconfig *)NULL)
376		goto error;
377
378	/* Transform addr to netbuf */
379	(void) memset(&netaddr, 0, sizeof (netaddr));
380	netaddr.buf = (char *)sin;
381
382	/* get an fd connected to the given address */
383	fd =  get_connection(nconf, netaddr);
384	if (fd == -1) {
385		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
386			"unable to open connection to ADMIN server "
387			"(t_error %i)"), t_errno);
388		code = KADM5_RPC_ERROR;
389		goto error;
390	}
391
392#ifdef DEBUG
393	printf("fd: %d, KADM: %d, KADMVERS %d\n", fd, KADM, KADMVERS);
394	printf("nconf: nc_netid: %s, nc_semantics: %d, nc_flag: %d, "
395	    "nc_protofmly: %s\n",
396	    nconf->nc_netid, nconf->nc_semantics, nconf->nc_flag,
397	    nconf->nc_protofmly);
398	printf("nc_proto: %s, nc_device: %s, nc_nlookups: %d, nc_used: %d\n",
399	    nconf->nc_proto, nconf->nc_device, nconf->nc_nlookups,
400	    nconf->nc_unused);
401	printf("netaddr: maxlen %d, buf: %s, len: %d\n", netaddr.maxlen,
402	    netaddr.buf, netaddr.len);
403#endif
404 	/*
405	 * Tell clnt_tli_create that given fd is already connected
406	 *
407	 * If the service_name and client_name are iprop-centric,
408	 * we need to clnt_tli_create to the appropriate RPC prog
409	 */
410	iprop_svc = strdup(KIPROP_SVC_NAME);
411	if (iprop_svc == NULL)
412		return (ENOMEM);
413
414	if ((strstr(service_name, iprop_svc) != NULL) &&
415	    (strstr(client_name, iprop_svc) != NULL)) {
416		iprop_enable = B_TRUE;
417		handle->clnt = clnt_tli_create(fd, nconf, NULL,
418				    KRB5_IPROP_PROG, KRB5_IPROP_VERS, 0, 0);
419	}
420	else
421		handle->clnt = clnt_tli_create(fd, nconf, NULL,
422				    KADM, KADMVERS, 0, 0);
423
424	if (iprop_svc)
425		free(iprop_svc);
426
427	if (handle->clnt == NULL) {
428		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
429					"clnt_tli_create failed\n"));
430		code = KADM5_RPC_ERROR;
431		(void) close(fd);
432		goto error;
433	}
434	/*
435	 * The rpc-handle was created on an fd opened and connected
436	 * by us, so we have to explicitly tell rpc to close it.
437	 */
438	if (clnt_control(handle->clnt, CLSET_FD_CLOSE, NULL) != TRUE) {
439		clnt_pcreateerror("ERROR:");
440		syslog(LOG_ERR, dgettext(TEXT_DOMAIN,
441			"clnt_control failed to set CLSET_FD_CLOSE"));
442		code = KADM5_RPC_ERROR;
443		(void) close(fd);
444		goto error;
445	}
446
447	handle->lhandle->clnt = handle->clnt;
448
449	/* now that handle->clnt is set, we can check the handle */
450	if (code = _kadm5_check_handle((void *) handle))
451		goto error;
452
453	/*
454	 * The RPC connection is open; establish the GSS-API
455	 * authentication context.
456	 */
457	ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN,
458				    "have an rpc connection open\n"));
459	/* use the kadm5 cache */
460	ccname_orig = getenv("KRB5CCNAME");
461	if (ccname_orig)
462		ccname_orig = strdup(ccname_orig);
463
464	(void) krb5_setenv("KRB5CCNAME", handle->cache_name, 1);
465
466	ADMIN_LOG(LOG_ERR,
467		dgettext(TEXT_DOMAIN,
468			"current credential cache: %s"), handle->cache_name);
469	input_name.value = client_name;
470	input_name.length = strlen((char *)input_name.value) + 1;
471	gssstat = gss_import_name(&minor_stat, &input_name,
472				(gss_OID)gss_nt_krb5_name, &gss_client);
473	if (gssstat != GSS_S_COMPLETE) {
474		code = KADM5_GSS_ERROR;
475		ADMIN_LOGO(LOG_ERR,
476			dgettext(TEXT_DOMAIN,
477				"gss_import_name failed for client name\n"));
478		goto error;
479	}
480
481	if (!rpc_gss_mech_to_oid(mech, (rpc_gss_OID *)&mech_oid)) {
482		ADMIN_LOG(LOG_ERR,
483			dgettext(TEXT_DOMAIN,
484				"Invalid mechanism oid <%s>"), mech);
485		goto error;
486	}
487
488	oid_set.count = 1;
489	oid_set.elements = mech_oid;
490
491	gssstat = gss_acquire_cred(&minor_stat, gss_client, 0,
492				&oid_set, GSS_C_INITIATE,
493				&gss_client_creds, NULL, NULL);
494	(void) gss_release_name(&minor_stat, &gss_client);
495	if (gssstat != GSS_S_COMPLETE) {
496		code = KADM5_GSS_ERROR;
497		ADMIN_LOG(LOG_ERR,
498			dgettext(TEXT_DOMAIN,
499				"could not acquire credentials, "
500				"major error code: %d\n"), gssstat);
501		goto error;
502	}
503	handle->my_cred = gss_client_creds;
504	options_req.my_cred = gss_client_creds;
505	options_req.req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG;
506	options_req.time_req = 0;
507	options_req.input_channel_bindings = NULL;
508#ifndef INIT_TEST
509	handle->clnt->cl_auth = rpc_gss_seccreate(handle->clnt,
510						service_name,
511						mech,
512						service,
513						NULL,
514						&options_req,
515						&options_ret);
516#endif /* ! INIT_TEST */
517
518	if (ccname_orig) {
519		(void) krb5_setenv("KRB5CCNAME", ccname_orig, 1);
520		free(ccname_orig);
521	} else
522		(void) krb5_unsetenv("KRB5CCNAME");
523
524	if (handle->clnt->cl_auth == NULL) {
525		code = KADM5_GSS_ERROR;
526		display_status(dgettext(TEXT_DOMAIN,
527					"rpc_gss_seccreate failed\n"),
528			    options_ret.major_status,
529			    options_ret.minor_status,
530			    mech);
531		goto error;
532	}
533
534	/*
535	 * Bypass the remainder of the code and return straightaway
536	 * if the gss service requested is kiprop
537	 */
538	if (iprop_enable == B_TRUE) {
539		code = 0;
540		goto cleanup;
541	}
542
543	r = init_2(&handle->api_version, handle->clnt);
544	/* Solaris Kerberos: 163 resync */
545	if (r == NULL) {
546		ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN,
547			"error during admin api initialization\n"));
548		code = KADM5_RPC_ERROR;
549		goto error;
550	}
551
552	if (r->code) {
553		code = r->code;
554		ADMIN_LOG(LOG_ERR,
555			dgettext(TEXT_DOMAIN,
556				"error during admin api initialization: %d\n"),
557			r->code);
558		goto error;
559	}
560error:
561cleanup:
562
563	if (handlep != (void *) NULL)
564		(void) endnetconfig(handlep);
565	/*
566	 * gss_client_creds is freed only when there is an error condition,
567	 * given that rpc_gss_seccreate() will assign the cred pointer to the
568	 * my_cred member in the auth handle's private data structure.
569	 */
570	if (code && (gss_client_creds != GSS_C_NO_CREDENTIAL))
571		(void) gss_release_cred(&minor_stat, &gss_client_creds);
572
573	return (code);
574}
575
576static kadm5_ret_t _kadm5_init_any(char *client_name,
577				   enum init_type init_type,
578				   char *pass,
579				   krb5_ccache ccache_in,
580				   char *service_name,
581				   kadm5_config_params *params_in,
582				   krb5_ui_4 struct_version,
583				   krb5_ui_4 api_version,
584				   char **db_args,
585				   void **server_handle)
586{
587     int i;
588     krb5_creds	creds;
589     krb5_ccache ccache = NULL;
590     krb5_timestamp  now;
591     OM_uint32 gssstat, minor_stat;
592     kadm5_server_handle_t handle;
593     kadm5_config_params params_local;
594     int code = 0;
595     krb5_get_init_creds_opt opt;
596     gss_buffer_desc input_name;
597     krb5_error_code kret;
598     krb5_int32 starttime;
599     char *server = NULL;
600     krb5_principal serverp = NULL, clientp = NULL;
601     krb5_principal saved_server = NULL;
602     bool_t cpw = FALSE;
603
604	ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN,
605		"entering kadm5_init_any\n"));
606     if (! server_handle) {
607	 return EINVAL;
608     }
609
610     if (! (handle = malloc(sizeof(*handle)))) {
611	  return ENOMEM;
612     }
613     if (! (handle->lhandle = malloc(sizeof(*handle)))) {
614	  free(handle);
615	  return ENOMEM;
616     }
617
618     handle->magic_number = KADM5_SERVER_HANDLE_MAGIC;
619     handle->struct_version = struct_version;
620     handle->api_version = api_version;
621     handle->clnt = 0;
622     handle->cache_name = 0;
623     handle->destroy_cache = 0;
624     *handle->lhandle = *handle;
625     handle->lhandle->api_version = KADM5_API_VERSION_2;
626     handle->lhandle->struct_version = KADM5_STRUCT_VERSION;
627     handle->lhandle->lhandle = handle->lhandle;
628
629    kret = krb5_init_context(&handle->context);
630	if (kret) {
631		free(handle->lhandle);
632		free(handle);
633		return (kret);
634	}
635
636     if(service_name == NULL || client_name == NULL) {
637	krb5_free_context(handle->context);
638	free(handle->lhandle);
639	free(handle);
640	return EINVAL;
641     }
642     memset((char *) &creds, 0, sizeof(creds));
643
644     /*
645      * Verify the version numbers before proceeding; we can't use
646      * CHECK_HANDLE because not all fields are set yet.
647      */
648     GENERIC_CHECK_HANDLE(handle, KADM5_OLD_LIB_API_VERSION,
649			  KADM5_NEW_LIB_API_VERSION);
650
651     /*
652      * Acquire relevant profile entries.  In version 2, merge values
653      * in params_in with values from profile, based on
654      * params_in->mask.
655      *
656      * In version 1, we've given a realm (which may be NULL) instead
657      * of params_in.  So use that realm, make params_in contain an
658      * empty mask, and behave like version 2.
659      */
660     memset((char *) &params_local, 0, sizeof(params_local));
661     if (api_version == KADM5_API_VERSION_1) {
662	  if (params_in)
663	       params_local.mask = KADM5_CONFIG_REALM;
664	  params_in = &params_local;
665	}
666
667#define ILLEGAL_PARAMS ( \
668		KADM5_CONFIG_ACL_FILE	| KADM5_CONFIG_ADB_LOCKFILE | \
669		KADM5_CONFIG_DBNAME	| KADM5_CONFIG_ADBNAME | \
670		KADM5_CONFIG_DICT_FILE	| KADM5_CONFIG_ADMIN_KEYTAB | \
671			KADM5_CONFIG_STASH_FILE | KADM5_CONFIG_MKEY_NAME | \
672			KADM5_CONFIG_ENCTYPE	| KADM5_CONFIG_MAX_LIFE	| \
673			KADM5_CONFIG_MAX_RLIFE	| KADM5_CONFIG_EXPIRATION | \
674			KADM5_CONFIG_FLAGS	| KADM5_CONFIG_ENCTYPES	| \
675			KADM5_CONFIG_MKEY_FROM_KBD)
676
677     if (params_in && params_in->mask & ILLEGAL_PARAMS) {
678		krb5_free_context(handle->context);
679		free(handle->lhandle);
680	  free(handle);
681		ADMIN_LOG(LOG_ERR, dgettext(TEXT_DOMAIN,
682			"bad client parameters, returning %d"),
683			KADM5_BAD_CLIENT_PARAMS);
684	  return KADM5_BAD_CLIENT_PARAMS;
685     }
686
687     if ((code = kadm5_get_config_params(handle->context, 0,
688					 params_in, &handle->params))) {
689	  krb5_free_context(handle->context);
690	  free(handle->lhandle);
691	  free(handle);
692		ADMIN_LOG(LOG_ERR, dgettext(TEXT_DOMAIN,
693			"failed to get config_params, return: %d\n"), code);
694	  return(code);
695     }
696
697#define REQUIRED_PARAMS (KADM5_CONFIG_REALM | \
698			 KADM5_CONFIG_ADMIN_SERVER | \
699			 KADM5_CONFIG_KADMIND_PORT)
700#define KPW_REQUIRED_PARAMS (KADM5_CONFIG_REALM | \
701			 KADM5_CONFIG_KPASSWD_SERVER | \
702			 KADM5_CONFIG_KPASSWD_PORT)
703
704     if (((handle->params.mask & REQUIRED_PARAMS) != REQUIRED_PARAMS) &&
705	 ((handle->params.mask & KPW_REQUIRED_PARAMS) != KPW_REQUIRED_PARAMS)) {
706		(void) kadm5_free_config_params(handle->context,
707						&handle->params);
708	  krb5_free_context(handle->context);
709		free(handle->lhandle);
710	  free(handle);
711		ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN,
712			"missing config parameters\n"));
713	  return KADM5_MISSING_KRB5_CONF_PARAMS;
714     }
715
716	/*
717	 * Acquire a service ticket for service_name@realm in the name of
718	 * client_name, using password pass (which could be NULL), and
719	 * create a ccache to store them in.  If INIT_CREDS, use the
720	 * ccache we were provided instead.
721	 */
722	if ((code = krb5_parse_name(handle->context, client_name,
723			    &creds.client))) {
724		ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN,
725			    "could not parse client name\n"));
726		goto error;
727	}
728	clientp = creds.client;
729
730	if (strncmp(service_name, KADM5_CHANGEPW_HOST_SERVICE,
731	    strlen(KADM5_CHANGEPW_HOST_SERVICE)) == 0)
732		cpw = TRUE;
733
734	if (init_type == INIT_PASS &&
735	    handle->params.kpasswd_protocol == KRB5_CHGPWD_CHANGEPW_V2 &&
736	    cpw == TRUE) {
737		/*
738		 * The 'service_name' is constructed by the caller
739		 * but its done before the parameter which determines
740		 * the kpasswd_protocol is found.  The servers that
741		 * support the SET/CHANGE password protocol expect
742		 * a slightly different service principal than
743		 * the normal SEAM kadmind so construct the correct
744		 * name here and then forget it.
745		 */
746		char *newsvcname = NULL;
747		newsvcname = malloc(strlen(KADM5_CHANGEPW_SERVICE) +
748				    strlen(handle->params.realm) + 2);
749		if (newsvcname == NULL) {
750			ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN,
751					    "could not malloc\n"));
752			code = ENOMEM;
753			goto error;
754		}
755		sprintf(newsvcname, "%s@%s", KADM5_CHANGEPW_SERVICE,
756			handle->params.realm);
757
758		if ((code = krb5_parse_name(handle->context, newsvcname,
759					    &creds.server))) {
760			ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN,
761					    "could not parse server "
762					    "name\n"));
763			free(newsvcname);
764			goto error;
765		}
766		free(newsvcname);
767	} else {
768		input_name.value = service_name;
769		input_name.length = strlen((char *)input_name.value) + 1;
770		gssstat = krb5_gss_import_name(&minor_stat,
771				    &input_name,
772				    (gss_OID)GSS_C_NT_HOSTBASED_SERVICE,
773				    (gss_name_t *)&creds.server);
774
775		if (gssstat != GSS_S_COMPLETE) {
776			code = KADM5_GSS_ERROR;
777			ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN,
778				"gss_import_name failed for client name\n"));
779			goto error;
780		}
781	}
782	serverp = creds.server;
783
784	/* XXX temporarily fix a bug in krb5_cc_get_type */
785#undef krb5_cc_get_type
786#define krb5_cc_get_type(context, cache) ((cache)->ops->prefix)
787
788
789     if (init_type == INIT_CREDS) {
790	  ccache = ccache_in;
791	  handle->cache_name = (char *)
792	       malloc(strlen(krb5_cc_get_type(handle->context, ccache)) +
793		      strlen(krb5_cc_get_name(handle->context, ccache)) + 2);
794	  if (handle->cache_name == NULL) {
795	       code = ENOMEM;
796	       goto error;
797	  }
798	  sprintf(handle->cache_name, "%s:%s",
799		  krb5_cc_get_type(handle->context, ccache),
800		  krb5_cc_get_name(handle->context, ccache));
801     } else {
802#if 0
803	  handle->cache_name =
804	       (char *) malloc(strlen(ADM_CCACHE)+strlen("FILE:")+1);
805	  if (handle->cache_name == NULL) {
806	       code = ENOMEM;
807	       goto error;
808	  }
809	  sprintf(handle->cache_name, "FILE:%s", ADM_CCACHE);
810	  mktemp(handle->cache_name + strlen("FILE:"));
811#endif
812	  {
813	      static int counter = 0;
814	      handle->cache_name = malloc(sizeof("MEMORY:kadm5_")
815					  + 3*sizeof(counter));
816	      sprintf(handle->cache_name, "MEMORY:kadm5_%u", counter++);
817	  }
818
819	  if ((code = krb5_cc_resolve(handle->context, handle->cache_name,
820				      &ccache)))
821	       goto error;
822
823	  if ((code = krb5_cc_initialize (handle->context, ccache,
824					  creds.client)))
825	       goto error;
826
827	  handle->destroy_cache = 1;
828     }
829     handle->lhandle->cache_name = handle->cache_name;
830	ADMIN_LOG(LOG_ERR, dgettext(TEXT_DOMAIN,
831		"cache created: %s\n"), handle->cache_name);
832
833     if ((code = krb5_timeofday(handle->context, &now)))
834	  goto error;
835
836     /*
837      * Get a ticket, use the method specified in init_type.
838      */
839
840     creds.times.starttime = 0; /* start timer at KDC */
841     creds.times.endtime = 0; /* endtime will be limited by service */
842
843	memset(&opt, 0, sizeof (opt));
844	krb5_get_init_creds_opt_init(&opt);
845
846	if (creds.times.endtime) {
847		if (creds.times.starttime)
848			starttime = creds.times.starttime;
849		else
850			starttime = now;
851
852		krb5_get_init_creds_opt_set_tkt_life(&opt,
853			creds.times.endtime - starttime);
854	}
855	code = krb5_unparse_name(handle->context, creds.server, &server);
856	if (code)
857		goto error;
858
859	/*
860	 * Solaris Kerberos:
861	 * Save the original creds.server as krb5_get_init_creds*() always
862	 * sets the realm of the server to the client realm.
863	 */
864	code = krb5_copy_principal(handle->context, creds.server, &saved_server);
865	if (code)
866		goto error;
867
868	if (init_type == INIT_PASS) {
869		code = krb5_get_init_creds_password(handle->context,
870			&creds, creds.client, pass, NULL,
871			NULL, creds.times.starttime,
872			server, &opt);
873	} else if (init_type == INIT_SKEY) {
874		krb5_keytab kt = NULL;
875
876		if (!(pass && (code = krb5_kt_resolve(handle->context,
877					pass, &kt)))) {
878			code = krb5_get_init_creds_keytab(
879					handle->context,
880					&creds, creds.client, kt,
881					creds.times.starttime,
882					server, &opt);
883
884	       if (pass) krb5_kt_close(handle->context, kt);
885	  }
886     }
887
888     /* Improved error messages */
889     if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY) code = KADM5_BAD_PASSWORD;
890     if (code == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN)
891	  code = KADM5_SECURE_PRINC_MISSING;
892
893     if (code != 0) {
894		ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN,
895			"failed to obtain credentials cache\n"));
896		krb5_free_principal(handle->context, saved_server);
897		goto error;
898	}
899
900	/*
901	 * Solaris Kerberos:
902	 * If the server principal had an empty realm then store that in
903	 * the cred cache and not the server realm as returned by
904	 * krb5_get_init_creds_{keytab|password}(). This ensures that rpcsec_gss
905	 * will find the credential in the cred cache even if a "fallback"
906	 * method is being used to determine the realm.
907	 */
908	if (init_type != INIT_CREDS) {
909		krb5_free_principal(handle->context, creds.server);
910	}
911	creds.server = saved_server;
912
913	/*
914	 * If we got this far, save the creds in the cache.
915	 */
916	if (ccache) {
917		code = krb5_cc_store_cred(handle->context, ccache, &creds);
918	}
919
920	ADMIN_LOGO(LOG_ERR, dgettext(TEXT_DOMAIN, "obtained credentials cache\n"));
921
922#ifdef ZEROPASSWD
923     if (pass != NULL)
924	  memset(pass, 0, strlen(pass));
925#endif
926
927	if (init_type != INIT_PASS ||
928	    handle->params.kpasswd_protocol == KRB5_CHGPWD_RPCSEC ||
929	    cpw == FALSE) {
930		code = _kadm5_initialize_rpcsec_gss_handle(handle,
931					client_name, service_name);
932
933		/*
934		 * Solaris Kerberos:
935		 * If _kadm5_initialize_rpcsec_gss_handle() fails it will have
936		 * called krb5_gss_release_cred(). If the credential cache is a
937		 * MEMORY cred cache krb5_gss_release_cred() destroys the
938		 * cred cache data. Make sure that the cred-cache is closed
939		 * to prevent a double free in the "error" code.
940		 */
941		if (code != 0) {
942			if (init_type != INIT_CREDS) {
943				krb5_cc_close(handle->context, ccache);
944				ccache = NULL;
945			}
946			goto error;
947		}
948	}
949
950	*server_handle = (void *) handle;
951
952	if (init_type != INIT_CREDS)
953		krb5_cc_close(handle->context, ccache);
954
955	goto cleanup;
956
957error:
958     /*
959      * Note that it is illegal for this code to execute if "handle"
960      * has not been allocated and initialized.  I.e., don't use "goto
961      * error" before the block of code at the top of the function
962      * that allocates and initializes "handle".
963      */
964     if (handle->cache_name)
965	 free(handle->cache_name);
966     if (handle->destroy_cache && ccache)
967	 krb5_cc_destroy(handle->context, ccache);
968     if(handle->clnt && handle->clnt->cl_auth)
969	  AUTH_DESTROY(handle->clnt->cl_auth);
970     if(handle->clnt)
971	  clnt_destroy(handle->clnt);
972	(void) kadm5_free_config_params(handle->context, &handle->params);
973
974cleanup:
975	if (server)
976		free(server);
977
978	/*
979	 * cred's server and client pointers could have been overwritten
980	 * by the krb5_get_init_* functions.  If the addresses are different
981	 * before and after the calls then we must free the memory that
982	 * was allocated before the call.
983	 */
984	if (clientp && clientp != creds.client)
985		krb5_free_principal(handle->context, clientp);
986
987	if (serverp && serverp != creds.server)
988		krb5_free_principal(handle->context, serverp);
989
990     krb5_free_cred_contents(handle->context, &creds);
991
992	/*
993	 * Dont clean up the handle if the code is OK (code==0)
994	 * because it is returned to the caller in the 'server_handle'
995	 * ptr.
996	 */
997     if (code) {
998		krb5_free_context(handle->context);
999		free(handle->lhandle);
1000	  free(handle);
1001	}
1002
1003     return code;
1004}
1005
1006kadm5_ret_t
1007kadm5_destroy(void *server_handle)
1008{
1009     krb5_ccache	    ccache = NULL;
1010     int		    code = KADM5_OK;
1011     kadm5_server_handle_t	handle =
1012	  (kadm5_server_handle_t) server_handle;
1013	OM_uint32 min_stat;
1014
1015     CHECK_HANDLE(server_handle);
1016/* SUNW14resync:
1017 * krb5_cc_resolve() will resolve a ccache with the same data that
1018 * handle->my_cred points to. If the ccache is a MEMORY ccache then
1019 * gss_release_cred() will free that data (it doesn't do this when ccache
1020 * is a FILE ccache).
1021 * if'ed out to avoid the double free.
1022 */
1023#if 0
1024     if (handle->destroy_cache && handle->cache_name) {
1025	 if ((code = krb5_cc_resolve(handle->context,
1026				     handle->cache_name, &ccache)) == 0)
1027	     code = krb5_cc_destroy (handle->context, ccache);
1028     }
1029#endif
1030     if (handle->cache_name)
1031	 free(handle->cache_name);
1032     if (handle->clnt && handle->clnt->cl_auth) {
1033		/*
1034		 * Since kadm5 doesn't use the default credentials we
1035		 * must clean this up manually.
1036		 */
1037		if (handle->my_cred != GSS_C_NO_CREDENTIAL)
1038			(void) gss_release_cred(&min_stat, &handle->my_cred);
1039	  AUTH_DESTROY(handle->clnt->cl_auth);
1040	}
1041     if (handle->clnt)
1042	  clnt_destroy(handle->clnt);
1043     if (handle->lhandle)
1044          free (handle->lhandle);
1045
1046     kadm5_free_config_params(handle->context, &handle->params);
1047     krb5_free_context(handle->context);
1048
1049     handle->magic_number = 0;
1050     free(handle);
1051
1052     return code;
1053}
1054/* not supported on client */
1055kadm5_ret_t kadm5_lock(void *server_handle)
1056{
1057    return EINVAL;
1058}
1059
1060/* not supported on client */
1061kadm5_ret_t kadm5_unlock(void *server_handle)
1062{
1063    return EINVAL;
1064}
1065
1066kadm5_ret_t kadm5_flush(void *server_handle)
1067{
1068     return KADM5_OK;
1069}
1070
1071int _kadm5_check_handle(void *handle)
1072{
1073     CHECK_HANDLE(handle);
1074     return 0;
1075}
1076
1077krb5_error_code kadm5_init_krb5_context (krb5_context *ctx)
1078{
1079    return krb5_init_context(ctx);
1080}
1081
1082/*
1083 * Stub function for kadmin.  It was created to eliminate the dependency on
1084 * libkdb's ulog functions.  The srv equivalent makes the actual calls.
1085 */
1086krb5_error_code
1087kadm5_init_iprop(void *handle)
1088{
1089	return (0);
1090}
1091