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 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 * 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 * 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 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 * 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 * 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 * 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 * 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 * 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 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 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