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