xref: /freebsd/crypto/krb5/src/kadmin/server/ipropd_svc.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-file-style: "bsd"; indent-tabs-mode: t -*- */
2 /*
3  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
4  * Use is subject to license terms.
5  */
6 
7 /* #pragma ident	"@(#)ipropd_svc.c	1.2	04/02/20 SMI" */
8 
9 
10 #include "k5-platform.h"
11 #include <signal.h>
12 #include <sys/types.h>
13 #include <sys/resource.h> /* rlimit */
14 #include <syslog.h>
15 
16 #include <kadm5/admin.h>
17 #include <kadm5/kadm_rpc.h>
18 #include <kadm5/server_internal.h>
19 #include <adm_proto.h>
20 #include <string.h>
21 #include <gssapi_krb5.h>
22 #include <sys/socket.h>
23 #include <netinet/in.h>
24 #include <arpa/inet.h>
25 #include <netdb.h>
26 #include <kdb_log.h>
27 #include "auth.h"
28 #include "misc.h"
29 #include "osconf.h"
30 
31 extern gss_name_t rqst2name(struct svc_req *rqstp);
32 
33 extern void *global_server_handle;
34 extern int nofork;
35 extern short l_port;
36 extern char *kdb5_util;
37 extern char *kprop;
38 extern char *dump_file;
39 extern char *kprop_port;
40 
41 static char *reply_ok_str	= "UPDATE_OK";
42 static char *reply_err_str	= "UPDATE_ERROR";
43 static char *reply_fr_str	= "UPDATE_FULL_RESYNC_NEEDED";
44 static char *reply_busy_str	= "UPDATE_BUSY";
45 static char *reply_nil_str	= "UPDATE_NIL";
46 static char *reply_perm_str	= "UPDATE_PERM_DENIED";
47 static char *reply_unknown_str	= "<UNKNOWN_CODE>";
48 
49 #define	LOG_UNAUTH  _("Unauthorized request: %s, client=%s, service=%s, addr=%s")
50 #define	LOG_DONE    _("Request: %s, %s, %s, client=%s, service=%s, addr=%s")
51 
52 #ifdef	DPRINT
53 #undef	DPRINT
54 #endif
55 #ifdef DEBUG
56 #define	DPRINT(...)				\
57     do {					\
58 	if (nofork) {				\
59 	    fprintf(stderr, __VA_ARGS__);	\
60 	    fflush(stderr);			\
61 	}					\
62     } while (0)
63 #else
64 #define	DPRINT(...)
65 #endif
66 
67 static void
debprret(char * w,update_status_t ret,kdb_sno_t sno)68 debprret(char *w, update_status_t ret, kdb_sno_t sno)
69 {
70     switch (ret) {
71     case UPDATE_OK:
72 	printf("%s: end (OK, sno=%u)\n",
73 	       w, sno);
74 	break;
75     case UPDATE_ERROR:
76 	printf("%s: end (ERROR)\n", w);
77 	break;
78     case UPDATE_FULL_RESYNC_NEEDED:
79 	printf("%s: end (FR NEEDED)\n", w);
80 	break;
81     case UPDATE_BUSY:
82 	printf("%s: end (BUSY)\n", w);
83 	break;
84     case UPDATE_NIL:
85 	printf("%s: end (NIL)\n", w);
86 	break;
87     case UPDATE_PERM_DENIED:
88 	printf("%s: end (PERM)\n", w);
89 	break;
90     default:
91 	printf("%s: end (UNKNOWN return code (%d))\n", w, ret);
92     }
93 }
94 
95 static char *
replystr(update_status_t ret)96 replystr(update_status_t ret)
97 {
98     switch (ret) {
99     case UPDATE_OK:
100 	return (reply_ok_str);
101     case UPDATE_ERROR:
102 	return (reply_err_str);
103     case UPDATE_FULL_RESYNC_NEEDED:
104 	return (reply_fr_str);
105     case UPDATE_BUSY:
106 	return (reply_busy_str);
107     case UPDATE_NIL:
108 	return (reply_nil_str);
109     case UPDATE_PERM_DENIED:
110 	return (reply_perm_str);
111     default:
112 	return (reply_unknown_str);
113     }
114 }
115 
116 /* Returns null on allocation failure.
117    Regardless of success or failure, frees the input buffer.  */
118 static char *
buf_to_string(gss_buffer_desc * b)119 buf_to_string(gss_buffer_desc *b)
120 {
121     OM_uint32 min_stat;
122     char *s = malloc(b->length+1);
123 
124     if (s) {
125 	memcpy(s, b->value, b->length);
126 	s[b->length] = 0;
127     }
128     (void) gss_release_buffer(&min_stat, b);
129     return s;
130 }
131 
132 static krb5_boolean
iprop_acl_check(krb5_context context,const char * client_name)133 iprop_acl_check(krb5_context context, const char *client_name)
134 {
135     krb5_principal client_princ;
136     krb5_boolean result;
137 
138     if (krb5_parse_name(context, client_name, &client_princ) != 0)
139 	return FALSE;
140     result = auth(context, OP_IPROP, client_princ,
141 		  NULL, NULL, NULL, NULL, NULL, 0);
142     krb5_free_principal(context, client_princ);
143     return result;
144 }
145 
146 kdb_incr_result_t *
iprop_get_updates_1_svc(kdb_last_t * arg,struct svc_req * rqstp)147 iprop_get_updates_1_svc(kdb_last_t *arg, struct svc_req *rqstp)
148 {
149     static kdb_incr_result_t ret;
150     char *whoami = "iprop_get_updates_1";
151     int kret;
152     kadm5_server_handle_t handle = global_server_handle;
153     char *client_name = 0, *service_name = 0;
154     char obuf[256] = {0};
155 
156     /* default return code */
157     ret.ret = UPDATE_ERROR;
158 
159     DPRINT("%s: start, last_sno=%lu\n", whoami,
160 	    (unsigned long)arg->last_sno);
161 
162     if (!handle) {
163 	krb5_klog_syslog(LOG_ERR,
164 			 _("%s: server handle is NULL"),
165 			 whoami);
166 	goto out;
167     }
168 
169     {
170 	gss_buffer_desc client_desc, service_desc;
171 
172 	if (setup_gss_names(rqstp, &client_desc, &service_desc) < 0) {
173 	    krb5_klog_syslog(LOG_ERR,
174 			     _("%s: setup_gss_names failed"),
175 			     whoami);
176 	    goto out;
177 	}
178 	client_name = buf_to_string(&client_desc);
179 	service_name = buf_to_string(&service_desc);
180 	if (client_name == NULL || service_name == NULL) {
181 	    krb5_klog_syslog(LOG_ERR,
182 			     _("%s: out of memory recording principal names"),
183 			     whoami);
184 	    goto out;
185 	}
186     }
187 
188     DPRINT("%s: clprinc=`%s'\n\tsvcprinc=`%s'\n", whoami, client_name,
189 	   service_name);
190 
191     if (!iprop_acl_check(handle->context, client_name)) {
192 	ret.ret = UPDATE_PERM_DENIED;
193 
194 	DPRINT("%s: PERMISSION DENIED: clprinc=`%s'\n\tsvcprinc=`%s'\n",
195 		whoami, client_name, service_name);
196 
197 	krb5_klog_syslog(LOG_NOTICE, LOG_UNAUTH, whoami,
198 			 client_name, service_name,
199 			 client_addr(rqstp->rq_xprt));
200 	goto out;
201     }
202 
203     kret = ulog_get_entries(handle->context, arg, &ret);
204 
205     if (ret.ret == UPDATE_OK) {
206 	(void) snprintf(obuf, sizeof (obuf),
207 			_("%s; Incoming SerialNo=%lu; Outgoing SerialNo=%lu"),
208 			replystr(ret.ret),
209 			(unsigned long)arg->last_sno,
210 			(unsigned long)ret.lastentry.last_sno);
211     } else {
212 	(void) snprintf(obuf, sizeof (obuf),
213 			_("%s; Incoming SerialNo=%lu; Outgoing SerialNo=N/A"),
214 			replystr(ret.ret),
215 			(unsigned long)arg->last_sno);
216     }
217 
218     DPRINT("%s: request %s %s\n\tclprinc=`%s'\n\tsvcprinc=`%s'\n",
219 	   whoami, obuf,
220 	   ((kret == 0) ? "success" : error_message(kret)),
221 	   client_name, service_name);
222 
223     krb5_klog_syslog(LOG_NOTICE,
224 		     _("Request: %s, %s, %s, client=%s, service=%s, addr=%s"),
225 		     whoami,
226 		     obuf,
227 		     ((kret == 0) ? "success" : error_message(kret)),
228 		     client_name, service_name,
229 		     client_addr(rqstp->rq_xprt));
230 
231 out:
232     if (nofork)
233 	debprret(whoami, ret.ret, ret.lastentry.last_sno);
234     free(client_name);
235     free(service_name);
236     return (&ret);
237 }
238 
239 
240 /*
241  * Given a client princ (foo/fqdn@R), copy (in arg cl) the fqdn substring.
242  * Return arg cl str ptr on success, else NULL.
243  */
244 static char *
getclhoststr(const char * clprinc,char * cl,size_t len)245 getclhoststr(const char *clprinc, char *cl, size_t len)
246 {
247     const char *s, *e;
248 
249     if ((s = strchr(clprinc, '/')) == NULL || (e = strchr(++s, '@')) == NULL ||
250 	(size_t)(e - s) >= len)
251 	return NULL;
252     memcpy(cl, s, e - s);
253     cl[e - s] = '\0';
254     return (cl);
255 }
256 
257 static kdb_fullresync_result_t *
ipropx_resync(uint32_t vers,struct svc_req * rqstp)258 ipropx_resync(uint32_t vers, struct svc_req *rqstp)
259 {
260     static kdb_fullresync_result_t ret;
261     char *ubuf = 0;
262     char clhost[NI_MAXHOST] = {0};
263     int pret, fret;
264     FILE *p;
265     kadm5_server_handle_t handle = global_server_handle;
266     char *client_name = NULL, *service_name = NULL;
267     char *whoami = "iprop_full_resync_1";
268 
269     /*
270      * vers contains the highest version number the client is
271      * willing to accept. A client can always accept a lower
272      * version: the version number is indicated in the dump
273      * header.
274      */
275 
276     /* default return code */
277     ret.ret = UPDATE_ERROR;
278 
279     if (!handle) {
280 	krb5_klog_syslog(LOG_ERR,
281 			 _("%s: server handle is NULL"),
282 			 whoami);
283 	goto out;
284     }
285 
286     DPRINT("%s: start\n", whoami);
287 
288     {
289 	gss_buffer_desc client_desc, service_desc;
290 
291 	if (setup_gss_names(rqstp, &client_desc, &service_desc) < 0) {
292 	    DPRINT("%s: setup_gss_names failed\n", whoami);
293 	    krb5_klog_syslog(LOG_ERR,
294 			     _("%s: setup_gss_names failed"),
295 			     whoami);
296 	    goto out;
297 	}
298 	client_name = buf_to_string(&client_desc);
299 	service_name = buf_to_string(&service_desc);
300 	if (client_name == NULL || service_name == NULL) {
301 	    DPRINT("%s: out of memory\n", whoami);
302 	    krb5_klog_syslog(LOG_ERR,
303 			     _("%s: out of memory recording principal names"),
304 			     whoami);
305 	    goto out;
306 	}
307     }
308 
309     DPRINT("%s: clprinc=`%s'\n\tsvcprinc=`%s'\n",
310 	    whoami, client_name, service_name);
311 
312     if (!iprop_acl_check(handle->context, client_name)) {
313 	ret.ret = UPDATE_PERM_DENIED;
314 
315 	DPRINT("%s: Permission denied\n", whoami);
316 	krb5_klog_syslog(LOG_NOTICE, LOG_UNAUTH, whoami,
317 			 client_name, service_name,
318 			 client_addr(rqstp->rq_xprt));
319 	goto out;
320     }
321 
322     if (!getclhoststr(client_name, clhost, sizeof (clhost))) {
323 	krb5_klog_syslog(LOG_ERR,
324 			 _("%s: getclhoststr failed"),
325 			 whoami);
326 	goto out;
327     }
328 
329     /*
330      * Note the -i; modified version of kdb5_util dump format
331      * to include sno (serial number). This argument is now
332      * versioned (-i0 for legacy dump format, -i1 for ipropx
333      * version 1 format, etc).
334      *
335      * The -c option ("conditional") causes the dump to dump only if no
336      * dump already exists or that dump is not in ipropx format, or the
337      * sno and timestamp in the header of that dump are outside the
338      * ulog.  This allows us to share a single global dump with all
339      * replicas, since it's OK to share an older dump, as long as its
340      * sno and timestamp are in the ulog (then the replicas can get the
341      * subsequent updates very iprop).
342      */
343     if (asprintf(&ubuf, "%s -r %s dump -i%d -c %s", kdb5_util,
344 		 handle->params.realm, vers, dump_file) < 0) {
345 	krb5_klog_syslog(LOG_ERR,
346 			 _("%s: cannot construct kdb5 util dump string too long; out of memory"),
347 			 whoami);
348 	goto out;
349     }
350 
351     /*
352      * Fork to dump the db and xfer it to the replica.
353      * (the fork allows parent to return quickly and the child
354      * acts like a callback to the replica).
355      */
356     fret = fork();
357     DPRINT("%s: fork=%d (%d)\n", whoami, fret, getpid());
358 
359     switch (fret) {
360     case -1: /* error */
361 	if (nofork) {
362 	    perror(whoami);
363 	}
364 	DPRINT("%s: fork failed\n", whoami);
365 	krb5_klog_syslog(LOG_ERR,
366 			 _("%s: fork failed: %s"),
367 			 whoami,
368 			 error_message(errno));
369 	goto out;
370 
371     case 0: /* child */
372 	DPRINT("%s: run `%s' ...\n", whoami, ubuf);
373 	(void) signal(SIGCHLD, SIG_DFL);
374 	/* run kdb5_util(1M) dump for IProp */
375 	p = popen(ubuf, "w");
376 	if (p == NULL) {
377 	    krb5_klog_syslog(LOG_ERR,
378 			     _("%s: popen failed: %s"),
379 			     whoami, error_message(errno));
380 	    _exit(1);
381 	}
382 	pret = pclose(p);
383 	DPRINT("%s: pclose=%d\n", whoami, pret);
384 	if (pret != 0) {
385 	    /* XXX popen/pclose may not set errno
386 	       properly, and the error could be from the
387 	       subprocess anyways.  */
388 	    if (nofork) {
389 		perror(whoami);
390 	    }
391 	    krb5_klog_syslog(LOG_ERR,
392 			     _("%s: pclose(popen) failed: %s"),
393 			     whoami,
394 			     error_message(errno));
395 	    _exit(1);
396 	}
397 
398 	if (kprop_port != NULL) {
399 	    DPRINT("%s: exec `kprop -r %s -f %s -P %s %s' ...\n",
400 		   whoami, handle->params.realm, dump_file, kprop_port,
401 		   clhost);
402 	    pret = execl(kprop, "kprop", "-r", handle->params.realm, "-f",
403 			 dump_file, "-P", kprop_port, clhost, NULL);
404 	} else {
405 	    DPRINT("%s: exec `kprop -r %s -f %s %s' ...\n",
406 		   whoami, handle->params.realm, dump_file, clhost);
407 	    pret = execl(kprop, "kprop", "-r", handle->params.realm, "-f",
408 			 dump_file, clhost, NULL);
409 	}
410 	perror(whoami);
411 	krb5_klog_syslog(LOG_ERR,
412 			 _("%s: exec failed: %s"),
413 			 whoami,
414 			 error_message(errno));
415 	_exit(1);
416 
417     default: /* parent */
418 	ret.ret = UPDATE_OK;
419 	/* not used by replica (sno is retrieved from kdb5_util dump) */
420 	ret.lastentry.last_sno = 0;
421 	ret.lastentry.last_time.seconds = 0;
422 	ret.lastentry.last_time.useconds = 0;
423 
424 	DPRINT("%s: spawned resync process %d, client=%s, "
425 		"service=%s, addr=%s\n", whoami, fret, client_name,
426 		service_name, client_addr(rqstp->rq_xprt));
427 	krb5_klog_syslog(LOG_NOTICE,
428 			 _("Request: %s, spawned resync process %d, client=%s, service=%s, addr=%s"),
429 			 whoami, fret,
430 			 client_name, service_name,
431 			 client_addr(rqstp->rq_xprt));
432 
433 	goto out;
434     }
435 
436 out:
437     if (nofork)
438 	debprret(whoami, ret.ret, 0);
439     free(client_name);
440     free(service_name);
441     free(ubuf);
442     return (&ret);
443 }
444 
445 kdb_fullresync_result_t *
iprop_full_resync_1_svc(void * argp,struct svc_req * rqstp)446 iprop_full_resync_1_svc(/* LINTED */ void *argp, struct svc_req *rqstp)
447 {
448     return ipropx_resync(IPROPX_VERSION_0, rqstp);
449 }
450 
451 kdb_fullresync_result_t *
iprop_full_resync_ext_1_svc(uint32_t * argp,struct svc_req * rqstp)452 iprop_full_resync_ext_1_svc(uint32_t *argp, struct svc_req *rqstp)
453 {
454     return ipropx_resync(*argp, rqstp);
455 }
456 
457 static int
check_iprop_rpcsec_auth(struct svc_req * rqstp)458 check_iprop_rpcsec_auth(struct svc_req *rqstp)
459 {
460     /* XXX Since the client can authenticate against any principal in
461        the database, we need to do a sanity check.  Only checking for
462        "kiprop" now, but that means theoretically the client could be
463        authenticating to kiprop on some other machine.  */
464     /* Code taken from kadm_rpc_svc.c, tweaked.  */
465 
466      gss_ctx_id_t ctx;
467      krb5_context kctx;
468      OM_uint32 maj_stat, min_stat;
469      gss_name_t name;
470      krb5_principal princ;
471      int ret, success;
472      krb5_data *c1, *realm;
473      gss_buffer_desc gss_str;
474      kadm5_server_handle_t handle;
475      size_t slen;
476      char *sdots;
477 
478      success = 0;
479      handle = (kadm5_server_handle_t)global_server_handle;
480 
481      if (rqstp->rq_cred.oa_flavor != RPCSEC_GSS)
482 	  return 0;
483 
484      ctx = rqstp->rq_svccred;
485 
486      maj_stat = gss_inquire_context(&min_stat, ctx, NULL, &name,
487 				    NULL, NULL, NULL, NULL, NULL);
488      if (maj_stat != GSS_S_COMPLETE) {
489 	  krb5_klog_syslog(LOG_ERR,
490 			   _("check_rpcsec_auth: failed inquire_context, "
491 			     "stat=%u"), maj_stat);
492 	  log_badauth(maj_stat, min_stat, rqstp->rq_xprt, NULL);
493 	  goto fail_name;
494      }
495 
496      kctx = handle->context;
497      ret = gss_to_krb5_name_1(rqstp, kctx, name, &princ, &gss_str);
498      if (ret == 0)
499 	  goto fail_name;
500 
501      slen = gss_str.length;
502      trunc_name(&slen, &sdots);
503      /*
504       * Since we accept with GSS_C_NO_NAME, the client can authenticate
505       * against the entire kdb.  Therefore, ensure that the service
506       * name is something reasonable.
507       */
508      if (krb5_princ_size(kctx, princ) != 2)
509 	  goto fail_princ;
510 
511      c1 = krb5_princ_component(kctx, princ, 0);
512      realm = krb5_princ_realm(kctx, princ);
513      if (strncmp(handle->params.realm, realm->data, realm->length) == 0
514 	 && strncmp("kiprop", c1->data, c1->length) == 0) {
515 	 success = 1;
516      }
517 
518 fail_princ:
519      if (!success) {
520 	  krb5_klog_syslog(LOG_ERR, _("bad service principal %.*s%s"),
521 			   (int) slen, (char *) gss_str.value, sdots);
522      }
523      gss_release_buffer(&min_stat, &gss_str);
524      krb5_free_principal(kctx, princ);
525 fail_name:
526      gss_release_name(&min_stat, &name);
527      return success;
528 }
529 
530 void
krb5_iprop_prog_1(struct svc_req * rqstp,SVCXPRT * transp)531 krb5_iprop_prog_1(struct svc_req *rqstp,
532 		  SVCXPRT *transp)
533 {
534     union {
535 	kdb_last_t iprop_get_updates_1_arg;
536     } argument;
537     void *result;
538     bool_t (*_xdr_argument)(), (*_xdr_result)();
539     void *(*local)(/* union XXX *, struct svc_req * */);
540     char *whoami = "krb5_iprop_prog_1";
541 
542     if (!check_iprop_rpcsec_auth(rqstp)) {
543 	krb5_klog_syslog(LOG_ERR, _("authentication attempt failed: %s, RPC "
544 				    "authentication flavor %d"),
545 			 client_addr(rqstp->rq_xprt),
546 			 rqstp->rq_cred.oa_flavor);
547 	svcerr_weakauth(transp);
548 	return;
549     }
550 
551     switch (rqstp->rq_proc) {
552     case NULLPROC:
553 	(void) svc_sendreply(transp, xdr_void,
554 			     (char *)NULL);
555 	return;
556 
557     case IPROP_GET_UPDATES:
558 	_xdr_argument = xdr_kdb_last_t;
559 	_xdr_result = xdr_kdb_incr_result_t;
560 	local = (void *(*)()) iprop_get_updates_1_svc;
561 	break;
562 
563     case IPROP_FULL_RESYNC:
564 	_xdr_argument = xdr_void;
565 	_xdr_result = xdr_kdb_fullresync_result_t;
566 	local = (void *(*)()) iprop_full_resync_1_svc;
567 	break;
568 
569     case IPROP_FULL_RESYNC_EXT:
570 	_xdr_argument = xdr_u_int32;
571 	_xdr_result = xdr_kdb_fullresync_result_t;
572 	local = (void *(*)()) iprop_full_resync_ext_1_svc;
573 	break;
574 
575     default:
576 	krb5_klog_syslog(LOG_ERR,
577 			 _("RPC unknown request: %d (%s)"),
578 			 rqstp->rq_proc, whoami);
579 	svcerr_noproc(transp);
580 	return;
581     }
582     (void) memset(&argument, 0, sizeof (argument));
583     if (!svc_getargs(transp, _xdr_argument, (caddr_t)&argument)) {
584 	krb5_klog_syslog(LOG_ERR,
585 			 _("RPC svc_getargs failed (%s)"),
586 			 whoami);
587 	svcerr_decode(transp);
588 	return;
589     }
590     result = (*local)(&argument, rqstp);
591 
592     if (_xdr_result && result != NULL &&
593 	!svc_sendreply(transp, _xdr_result, result)) {
594 	krb5_klog_syslog(LOG_ERR,
595 			 _("RPC svc_sendreply failed (%s)"),
596 			 whoami);
597 	svcerr_systemerr(transp);
598     }
599     if (!svc_freeargs(transp, _xdr_argument, (caddr_t)&argument)) {
600 	krb5_klog_syslog(LOG_ERR,
601 			 _("RPC svc_freeargs failed (%s)"),
602 			 whoami);
603 
604 	exit(1);
605     }
606 
607     if (rqstp->rq_proc == IPROP_GET_UPDATES) {
608 	/* LINTED */
609 	kdb_incr_result_t *r = (kdb_incr_result_t *)result;
610 
611 	if (r->ret == UPDATE_OK) {
612 	    ulog_free_entries(r->updates.kdb_ulog_t_val,
613 			      r->updates.kdb_ulog_t_len);
614 	    r->updates.kdb_ulog_t_val = NULL;
615 	    r->updates.kdb_ulog_t_len = 0;
616 	}
617     }
618 
619 }
620