1 /*
2  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 
9 #include <stdio.h>
10 #include <stdlib.h> /* getenv, exit */
11 #include <signal.h>
12 #include <sys/types.h>
13 #include <memory.h>
14 #include <stropts.h>
15 #include <netconfig.h>
16 #include <sys/resource.h> /* rlimit */
17 #include <syslog.h>
18 
19 #include <kadm5/admin.h>
20 #include <kadm5/kadm_rpc.h>
21 #include <kadm5/server_internal.h>
22 #include <server_acl.h>
23 #include <krb5/adm_proto.h>
24 #include <string.h>
25 #include <gssapi_krb5.h>
26 #include <sys/socket.h>
27 #include <netinet/in.h>
28 #include <arpa/inet.h>
29 #include <netdb.h>
30 #include <libintl.h>
31 #include <kdb/kdb_log.h>
32 #include "misc.h"
33 
34 
35 
36 extern int setup_gss_names(struct svc_req *, char **, char **);
37 extern gss_name_t get_clnt_name(struct svc_req *);
38 extern char *client_addr(struct svc_req *, char *);
39 extern void *global_server_handle;
40 extern int nofork;
41 extern short l_port;
42 static char abuf[33];
43 
44 static char *reply_ok_str	= "UPDATE_OK";
45 static char *reply_err_str	= "UPDATE_ERROR";
46 static char *reply_fr_str	= "UPDATE_FULL_RESYNC_NEEDED";
47 static char *reply_busy_str	= "UPDATE_BUSY";
48 static char *reply_nil_str	= "UPDATE_NIL";
49 static char *reply_perm_str	= "UPDATE_PERM_DENIED";
50 static char *reply_unknown_str	= "<UNKNOWN_CODE>";
51 
52 #define	LOG_UNAUTH  gettext("Unauthorized request: %s, %s, " \
53 			"client=%s, service=%s, addr=%s")
54 #define	LOG_DONE    gettext("Request: %s, %s, %s, client=%s, " \
55 			"service=%s, addr=%s")
56 
57 #define	KDB5_UTIL_DUMP_STR "/usr/sbin/kdb5_util dump -i "
58 
59 #ifdef	DPRINT
60 #undef	DPRINT
61 #endif
62 #define	DPRINT(i) if (nofork) printf i
63 
64 
65 static void
66 debprret(char *w, update_status_t ret, kdb_sno_t sno)
67 {
68 	switch (ret) {
69 	case UPDATE_OK:
70 		printf("%s: end (OK, sno=%u)\n",
71 		    w, sno);
72 		break;
73 	case UPDATE_ERROR:
74 		printf("%s: end (ERROR)\n", w);
75 		break;
76 	case UPDATE_FULL_RESYNC_NEEDED:
77 		printf("%s: end (FR NEEDED)\n", w);
78 		break;
79 	case UPDATE_BUSY:
80 		printf("%s: end (BUSY)\n", w);
81 		break;
82 	case UPDATE_NIL:
83 		printf("%s: end (NIL)\n", w);
84 		break;
85 	case UPDATE_PERM_DENIED:
86 		printf("%s: end (PERM)\n", w);
87 		break;
88 	default:
89 		printf("%s: end (UNKNOWN return code (%d))\n", w, ret);
90 	}
91 }
92 
93 static char *
94 replystr(update_status_t ret)
95 {
96 	switch (ret) {
97 	case UPDATE_OK:
98 		return (reply_ok_str);
99 	case UPDATE_ERROR:
100 		return (reply_err_str);
101 	case UPDATE_FULL_RESYNC_NEEDED:
102 		return (reply_fr_str);
103 	case UPDATE_BUSY:
104 		return (reply_busy_str);
105 	case UPDATE_NIL:
106 		return (reply_nil_str);
107 	case UPDATE_PERM_DENIED:
108 		return (reply_perm_str);
109 	default:
110 		return (reply_unknown_str);
111 	}
112 }
113 
114 kdb_incr_result_t *
115 iprop_get_updates_1(kdb_last_t *arg, struct svc_req *rqstp)
116 {
117 	static kdb_incr_result_t ret;
118 	char *whoami = "iprop_get_updates_1";
119 	int kret;
120 	kadm5_server_handle_t handle = global_server_handle;
121 	char *client_name = NULL, *service_name = NULL;
122 	gss_name_t name = NULL;
123 	OM_uint32 min_stat;
124 	char obuf[256] = {0};
125 
126 	/* default return code */
127 	ret.ret = UPDATE_ERROR;
128 
129 	DPRINT(("%s: start, last_sno=%u\n", whoami, (ulong_t)arg->last_sno));
130 
131 	if (!handle) {
132 		krb5_klog_syslog(LOG_ERR,
133 				gettext("%s: server handle is NULL"),
134 					whoami);
135 		goto out;
136 	}
137 
138 	if (setup_gss_names(rqstp, &client_name, &service_name) < 0) {
139 		krb5_klog_syslog(LOG_ERR,
140 			gettext("%s: setup_gss_names failed"),
141 			whoami);
142 		goto out;
143 	}
144 
145 	DPRINT(("%s: clprinc=`%s'\n\tsvcprinc=`%s'\n",
146 		whoami, client_name, service_name));
147 
148 	if (!(name = get_clnt_name(rqstp))) {
149 		krb5_klog_syslog(LOG_ERR,
150 			gettext("%s: Couldn't obtain client's name"),
151 			whoami);
152 		goto out;
153 	}
154 	if (!kadm5int_acl_check(handle->context,
155 		    name,
156 		    ACL_IPROP,
157 		    NULL,
158 		    NULL)) {
159 		ret.ret = UPDATE_PERM_DENIED;
160 
161 		audit_kadmind_unauth(rqstp->rq_xprt, l_port,
162 				    whoami,
163 				    "<null>", client_name);
164 		krb5_klog_syslog(LOG_NOTICE, LOG_UNAUTH, whoami,
165 				"<null>", client_name, service_name,
166 				client_addr(rqstp, abuf));
167 		goto out;
168 	}
169 
170 	kret = ulog_get_entries(handle->context, *arg, &ret);
171 
172 	if (ret.ret == UPDATE_OK) {
173 		(void) snprintf(obuf, sizeof (obuf),
174 		gettext("%s; Incoming SerialNo=%u; Outgoing SerialNo=%u"),
175 				replystr(ret.ret),
176 				(ulong_t)arg->last_sno,
177 				(ulong_t)ret.lastentry.last_sno);
178 	} else {
179 		(void) snprintf(obuf, sizeof (obuf),
180 		gettext("%s; Incoming SerialNo=%u; Outgoing SerialNo=N/A"),
181 				replystr(ret.ret),
182 				(ulong_t)arg->last_sno);
183 	}
184 
185 	audit_kadmind_auth(rqstp->rq_xprt, l_port,
186 			whoami,
187 			obuf, client_name, kret);
188 
189 	krb5_klog_syslog(LOG_NOTICE, LOG_DONE, whoami,
190 			obuf,
191 			((kret == 0) ? "success" : error_message(kret)),
192 			client_name, service_name,
193 			client_addr(rqstp, abuf));
194 
195 out:
196 	if (nofork)
197 		debprret(whoami, ret.ret, ret.lastentry.last_sno);
198 	if (client_name)
199 		free(client_name);
200 	if (service_name)
201 		free(service_name);
202 	if (name)
203 		gss_release_name(&min_stat, &name);
204 	return (&ret);
205 }
206 
207 
208 /*
209  * Given a client princ (foo/fqdn@R), copy (in arg cl) the fqdn substring.
210  * Return arg cl str ptr on success, else NULL.
211  */
212 static char *
213 getclhoststr(char *clprinc, char *cl, int len)
214 {
215 	char *s;
216 	if (s = strchr(clprinc, '/')) {
217 		if (!++s || strlcpy(cl, s, len) >= len) {
218 			return (NULL);
219 		}
220 		if (s = strchr(cl, '@')) {
221 			*s = '\0';
222 			return (cl); /* success */
223 		}
224 	}
225 
226 	return (NULL);
227 }
228 
229 kdb_fullresync_result_t *
230 iprop_full_resync_1(
231 	/* LINTED */
232 	void *argp,
233 	struct svc_req *rqstp)
234 {
235 	static kdb_fullresync_result_t ret;
236 	char tmpf[MAX_FILENAME] = {0};
237 	char ubuf[MAX_FILENAME + sizeof (KDB5_UTIL_DUMP_STR)] = {0};
238 	char clhost[MAXHOSTNAMELEN] = {0};
239 	int pret, fret;
240 	kadm5_server_handle_t handle = global_server_handle;
241 	OM_uint32 min_stat;
242 	gss_name_t name = NULL;
243 	char *client_name = NULL, *service_name = NULL;
244 	char *whoami = "iprop_full_resync_1";
245 
246 	/* default return code */
247 	ret.ret = UPDATE_ERROR;
248 
249 	if (!handle) {
250 		krb5_klog_syslog(LOG_ERR,
251 				gettext("%s: server handle is NULL"),
252 					whoami);
253 		goto out;
254 	}
255 
256 	DPRINT(("%s: start\n", whoami));
257 
258 	if (setup_gss_names(rqstp, &client_name, &service_name) < 0) {
259 		krb5_klog_syslog(LOG_ERR,
260 			gettext("%s: setup_gss_names failed"),
261 			whoami);
262 		goto out;
263 	}
264 
265 	DPRINT(("%s: clprinc=`%s'\n\tsvcprinc=`%s'\n",
266 		whoami, client_name, service_name));
267 
268 	if (!(name = get_clnt_name(rqstp))) {
269 		krb5_klog_syslog(LOG_ERR,
270 			gettext("%s: Couldn't obtain client's name"),
271 			whoami);
272 		goto out;
273 	}
274 	if (!kadm5int_acl_check(handle->context,
275 		    name,
276 		    ACL_IPROP,
277 		    NULL,
278 		    NULL)) {
279 		ret.ret = UPDATE_PERM_DENIED;
280 
281 		audit_kadmind_unauth(rqstp->rq_xprt, l_port,
282 				    whoami,
283 				    "<null>", client_name);
284 		krb5_klog_syslog(LOG_NOTICE, LOG_UNAUTH, whoami,
285 				"<null>", client_name, service_name,
286 				client_addr(rqstp, abuf));
287 		goto out;
288 	}
289 
290 	if (!getclhoststr(client_name, clhost, sizeof (clhost))) {
291 		krb5_klog_syslog(LOG_ERR,
292 			gettext("%s: getclhoststr failed"),
293 			whoami);
294 		goto out;
295 	}
296 
297 	/*
298 	 * construct db dump file name; kprop style name + clnt fqdn
299 	 */
300 	(void) strcpy(tmpf, "/var/krb5/slave_datatrans_");
301 	if (strlcat(tmpf, clhost, sizeof (tmpf)) >= sizeof (tmpf)) {
302 		krb5_klog_syslog(LOG_ERR,
303 		gettext("%s: db dump file name too long; max length=%d"),
304 				whoami,
305 				(sizeof (tmpf) - 1));
306 		goto out;
307 	}
308 
309 	/*
310 	 * note the -i; modified version of kdb5_util dump format
311 	 * to include sno (serial number)
312 	 */
313 	if (strlcpy(ubuf, KDB5_UTIL_DUMP_STR, sizeof (ubuf)) >=
314 	    sizeof (ubuf)) {
315 		goto out;
316 	}
317 	if (strlcat(ubuf, tmpf, sizeof (ubuf)) >= sizeof (ubuf)) {
318 		krb5_klog_syslog(LOG_ERR,
319 		gettext("%s: kdb5 util dump string too long; max length=%d"),
320 				whoami,
321 				(sizeof (ubuf) - 1));
322 		goto out;
323 	}
324 
325 	/*
326 	 * Fork to dump the db and xfer it to the slave.
327 	 * (the fork allows parent to return quickly and the child
328 	 * acts like a callback to the slave).
329 	 */
330 	fret = fork();
331 	DPRINT(("%s: fork=%d (%d)\n", whoami, fret, getpid()));
332 
333 	switch (fret) {
334 	case -1: /* error */
335 		if (nofork) {
336 			perror(whoami);
337 		}
338 		krb5_klog_syslog(LOG_ERR,
339 				gettext("%s: fork failed: %s"),
340 				whoami,
341 				error_message(errno));
342 		goto out;
343 
344 	case 0: /* child */
345 		DPRINT(("%s: run `%s' ...\n", whoami, ubuf));
346 		(void) signal(SIGCHLD, SIG_DFL);
347 		/* run kdb5_util(1M) dump for IProp */
348 		pret = pclose(popen(ubuf, "w"));
349 		DPRINT(("%s: pclose=%d\n", whoami, pret));
350 		if (pret == -1) {
351 			if (nofork) {
352 				perror(whoami);
353 			}
354 			krb5_klog_syslog(LOG_ERR,
355 				gettext("%s: pclose(popen) failed: %s"),
356 					whoami,
357 					error_message(errno));
358 			goto out;
359 		}
360 
361 		DPRINT(("%s: exec `kprop -f %s %s' ...\n",
362 			whoami, tmpf, clhost));
363 		pret = execl("/usr/lib/krb5/kprop", "kprop", "-f", tmpf,
364 			    clhost, NULL);
365 		if (pret == -1) {
366 			if (nofork) {
367 				perror(whoami);
368 			}
369 			krb5_klog_syslog(LOG_ERR,
370 					gettext("%s: exec failed: %s"),
371 					whoami,
372 					error_message(errno));
373 			goto out;
374 		}
375 
376 	default: /* parent */
377 		ret.ret = UPDATE_OK;
378 		/* not used by slave (sno is retrieved from kdb5_util dump) */
379 		ret.lastentry.last_sno = 0;
380 		ret.lastentry.last_time.seconds = 0;
381 		ret.lastentry.last_time.useconds = 0;
382 
383 		audit_kadmind_auth(rqstp->rq_xprt, l_port,
384 				whoami,
385 				"<null>", client_name, 0);
386 
387 		krb5_klog_syslog(LOG_NOTICE, LOG_DONE, whoami,
388 				"<null>",
389 				"success",
390 				client_name, service_name,
391 				client_addr(rqstp, abuf));
392 
393 		goto out;
394 	}
395 
396 out:
397 	if (nofork)
398 		debprret(whoami, ret.ret, 0);
399 	if (client_name)
400 		free(client_name);
401 	if (service_name)
402 		free(service_name);
403 	if (name)
404 		gss_release_name(&min_stat, &name);
405 	return (&ret);
406 }
407 
408 void
409 krb5_iprop_prog_1(
410 	struct svc_req *rqstp,
411 	register SVCXPRT *transp)
412 {
413 	union {
414 		kdb_last_t iprop_get_updates_1_arg;
415 	} argument;
416 	char *result;
417 	bool_t (*_xdr_argument)(), (*_xdr_result)();
418 	char *(*local)();
419 	char *whoami = "krb5_iprop_prog_1";
420 
421 	switch (rqstp->rq_proc) {
422 	case NULLPROC:
423 		(void) svc_sendreply(transp, xdr_void,
424 			(char *)NULL);
425 		return;
426 
427 	case IPROP_GET_UPDATES:
428 		_xdr_argument = xdr_kdb_last_t;
429 		_xdr_result = xdr_kdb_incr_result_t;
430 		local = (char *(*)()) iprop_get_updates_1;
431 		break;
432 
433 	case IPROP_FULL_RESYNC:
434 		_xdr_argument = xdr_void;
435 		_xdr_result = xdr_kdb_fullresync_result_t;
436 		local = (char *(*)()) iprop_full_resync_1;
437 		break;
438 
439 	default:
440 		krb5_klog_syslog(LOG_ERR,
441 				gettext("RPC unknown request: %d (%s)"),
442 				rqstp->rq_proc, whoami);
443 		svcerr_noproc(transp);
444 		return;
445 	}
446 	(void) memset((char *)&argument, 0, sizeof (argument));
447 	if (!svc_getargs(transp, _xdr_argument, (caddr_t)&argument)) {
448 		krb5_klog_syslog(LOG_ERR,
449 				gettext("RPC svc_getargs failed (%s)"),
450 				whoami);
451 		svcerr_decode(transp);
452 		return;
453 	}
454 	result = (*local)(&argument, rqstp);
455 
456 	if (_xdr_result && result != NULL &&
457 	    !svc_sendreply(transp, _xdr_result, result)) {
458 		krb5_klog_syslog(LOG_ERR,
459 				gettext("RPC svc_sendreply failed (%s)"),
460 				whoami);
461 		svcerr_systemerr(transp);
462 	}
463 	if (!svc_freeargs(transp, _xdr_argument, (caddr_t)&argument)) {
464 		krb5_klog_syslog(LOG_ERR,
465 				gettext("RPC svc_freeargs failed (%s)"),
466 				whoami);
467 
468 		exit(1);
469 	}
470 
471 	if (rqstp->rq_proc == IPROP_GET_UPDATES) {
472 		/* LINTED */
473 		kdb_incr_result_t *r = (kdb_incr_result_t *)result;
474 
475 		if (r->ret == UPDATE_OK) {
476 			ulog_free_entries(r->updates.kdb_ulog_t_val,
477 					r->updates.kdb_ulog_t_len);
478 			r->updates.kdb_ulog_t_val = NULL;
479 			r->updates.kdb_ulog_t_len = 0;
480 		}
481 	}
482 
483 }
484 
485 /*
486  * Get the host base service name for the kiprop principal. Returns
487  * KADM5_OK on success. Caller must free the storage allocated for
488  * host_service_name.
489  */
490 kadm5_ret_t
491 kiprop_get_adm_host_srv_name(
492 	krb5_context context,
493 	const char *realm,
494 	char **host_service_name)
495 {
496 	kadm5_ret_t ret;
497 	char *name;
498 	char *host;
499 
500 	if (ret = kadm5_get_master(context, realm, &host))
501 		return (ret);
502 
503 	name = malloc(strlen(KIPROP_SVC_NAME)+ strlen(host) + 2);
504 	if (name == NULL) {
505 		free(host);
506 		return (ENOMEM);
507 	}
508 	(void) sprintf(name, "%s@%s", KIPROP_SVC_NAME, host);
509 	free(host);
510 	*host_service_name = name;
511 
512 	return (KADM5_OK);
513 }
514