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 
71 static int old_auth_gssapi = 0;
72 /* connection timeout to kadmind in seconds */
73 #define		KADMIND_CONNECT_TIMEOUT	25
74 
75 int _kadm5_check_handle();
76 
77 enum init_type { INIT_PASS, INIT_SKEY, INIT_CREDS };
78 
79 static 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 
kadm5_init_with_creds(char * client_name,krb5_ccache ccache,char * service_name,kadm5_config_params * params,krb5_ui_4 struct_version,krb5_ui_4 api_version,char ** db_args,void ** server_handle)90 kadm5_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 
kadm5_init_with_password(char * client_name,char * pass,char * service_name,kadm5_config_params * params,krb5_ui_4 struct_version,krb5_ui_4 api_version,char ** db_args,void ** server_handle)106 kadm5_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 
kadm5_init(char * client_name,char * pass,char * service_name,kadm5_config_params * params,krb5_ui_4 struct_version,krb5_ui_4 api_version,char ** db_args,void ** server_handle)119 kadm5_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 
kadm5_init_with_skey(char * client_name,char * keytab,char * service_name,kadm5_config_params * params,krb5_ui_4 struct_version,krb5_ui_4 api_version,char ** db_args,void ** server_handle)132 kadm5_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 
145 krb5_error_code  kadm5_free_config_params();
146 
147 static void
display_status_1(m,code,type,mech)148 display_status_1(m, code, type, mech)
149 char *m;
150 OM_uint32 code;
151 int type;
152 const 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  */
203 void
display_status(msg,maj_stat,min_stat,mech)204 display_status(msg, maj_stat, min_stat, mech)
205 char *msg;
206 OM_uint32 maj_stat;
207 OM_uint32 min_stat;
208 char *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  */
228 static int
get_connection(struct netconfig * nconf,struct netbuf netaddr)229 get_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  */
301 static int
_kadm5_initialize_rpcsec_gss_handle(kadm5_server_handle_t handle,char * client_name,char * service_name)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 	}
560 error:
561 cleanup:
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 
_kadm5_init_any(char * client_name,enum init_type init_type,char * pass,krb5_ccache ccache_in,char * service_name,kadm5_config_params * params_in,krb5_ui_4 struct_version,krb5_ui_4 api_version,char ** db_args,void ** server_handle)576 static 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 
957 error:
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 
974 cleanup:
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 
1006 kadm5_ret_t
kadm5_destroy(void * server_handle)1007 kadm5_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 */
kadm5_lock(void * server_handle)1055 kadm5_ret_t kadm5_lock(void *server_handle)
1056 {
1057     return EINVAL;
1058 }
1059 
1060 /* not supported on client */
kadm5_unlock(void * server_handle)1061 kadm5_ret_t kadm5_unlock(void *server_handle)
1062 {
1063     return EINVAL;
1064 }
1065 
kadm5_flush(void * server_handle)1066 kadm5_ret_t kadm5_flush(void *server_handle)
1067 {
1068      return KADM5_OK;
1069 }
1070 
_kadm5_check_handle(void * handle)1071 int _kadm5_check_handle(void *handle)
1072 {
1073      CHECK_HANDLE(handle);
1074      return 0;
1075 }
1076 
kadm5_init_krb5_context(krb5_context * ctx)1077 krb5_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  */
1086 krb5_error_code
kadm5_init_iprop(void * handle)1087 kadm5_init_iprop(void *handle)
1088 {
1089 	return (0);
1090 }
1091