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