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