/* -*- mode: c; c-file-style: "bsd"; indent-tabs-mode: t -*- */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* #pragma ident "@(#)ipropd_svc.c 1.2 04/02/20 SMI" */ #include #include /* getenv, exit */ #include #include #include /* rlimit */ #include #include "k5-platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "misc.h" #include "osconf.h" extern gss_name_t rqst2name(struct svc_req *rqstp); extern void *global_server_handle; extern int nofork; extern short l_port; extern char *kdb5_util; extern char *kprop; extern char *dump_file; static char *reply_ok_str = "UPDATE_OK"; static char *reply_err_str = "UPDATE_ERROR"; static char *reply_fr_str = "UPDATE_FULL_RESYNC_NEEDED"; static char *reply_busy_str = "UPDATE_BUSY"; static char *reply_nil_str = "UPDATE_NIL"; static char *reply_perm_str = "UPDATE_PERM_DENIED"; static char *reply_unknown_str = ""; #define LOG_UNAUTH _("Unauthorized request: %s, client=%s, service=%s, addr=%s") #define LOG_DONE _("Request: %s, %s, %s, client=%s, service=%s, addr=%s") #ifdef DPRINT #undef DPRINT #endif #define DPRINT(...) \ do { \ if (nofork) { \ fprintf(stderr, __VA_ARGS__); \ fflush(stderr); \ } \ } while (0) static void debprret(char *w, update_status_t ret, kdb_sno_t sno) { switch (ret) { case UPDATE_OK: printf("%s: end (OK, sno=%u)\n", w, sno); break; case UPDATE_ERROR: printf("%s: end (ERROR)\n", w); break; case UPDATE_FULL_RESYNC_NEEDED: printf("%s: end (FR NEEDED)\n", w); break; case UPDATE_BUSY: printf("%s: end (BUSY)\n", w); break; case UPDATE_NIL: printf("%s: end (NIL)\n", w); break; case UPDATE_PERM_DENIED: printf("%s: end (PERM)\n", w); break; default: printf("%s: end (UNKNOWN return code (%d))\n", w, ret); } } static char * replystr(update_status_t ret) { switch (ret) { case UPDATE_OK: return (reply_ok_str); case UPDATE_ERROR: return (reply_err_str); case UPDATE_FULL_RESYNC_NEEDED: return (reply_fr_str); case UPDATE_BUSY: return (reply_busy_str); case UPDATE_NIL: return (reply_nil_str); case UPDATE_PERM_DENIED: return (reply_perm_str); default: return (reply_unknown_str); } } /* Returns null on allocation failure. Regardless of success or failure, frees the input buffer. */ static char * buf_to_string(gss_buffer_desc *b) { OM_uint32 min_stat; char *s = malloc(b->length+1); if (s) { memcpy(s, b->value, b->length); s[b->length] = 0; } (void) gss_release_buffer(&min_stat, b); return s; } kdb_incr_result_t * iprop_get_updates_1_svc(kdb_last_t *arg, struct svc_req *rqstp) { static kdb_incr_result_t ret; char *whoami = "iprop_get_updates_1"; int kret; kadm5_server_handle_t handle = global_server_handle; char *client_name = 0, *service_name = 0; char obuf[256] = {0}; /* default return code */ ret.ret = UPDATE_ERROR; DPRINT("%s: start, last_sno=%lu\n", whoami, (unsigned long)arg->last_sno); if (!handle) { krb5_klog_syslog(LOG_ERR, _("%s: server handle is NULL"), whoami); goto out; } { gss_buffer_desc client_desc, service_desc; if (setup_gss_names(rqstp, &client_desc, &service_desc) < 0) { krb5_klog_syslog(LOG_ERR, _("%s: setup_gss_names failed"), whoami); goto out; } client_name = buf_to_string(&client_desc); service_name = buf_to_string(&service_desc); if (client_name == NULL || service_name == NULL) { free(client_name); free(service_name); krb5_klog_syslog(LOG_ERR, _("%s: out of memory recording principal names"), whoami); goto out; } } DPRINT("%s: clprinc=`%s'\n\tsvcprinc=`%s'\n", whoami, client_name, service_name); if (!kadm5int_acl_check(handle->context, rqst2name(rqstp), ACL_IPROP, NULL, NULL)) { ret.ret = UPDATE_PERM_DENIED; DPRINT("%s: PERMISSION DENIED: clprinc=`%s'\n\tsvcprinc=`%s'\n", whoami, client_name, service_name); krb5_klog_syslog(LOG_NOTICE, LOG_UNAUTH, whoami, client_name, service_name, client_addr(rqstp->rq_xprt)); goto out; } kret = ulog_get_entries(handle->context, arg, &ret); if (ret.ret == UPDATE_OK) { (void) snprintf(obuf, sizeof (obuf), _("%s; Incoming SerialNo=%lu; Outgoing SerialNo=%lu"), replystr(ret.ret), (unsigned long)arg->last_sno, (unsigned long)ret.lastentry.last_sno); } else { (void) snprintf(obuf, sizeof (obuf), _("%s; Incoming SerialNo=%lu; Outgoing SerialNo=N/A"), replystr(ret.ret), (unsigned long)arg->last_sno); } DPRINT("%s: request %s %s\n\tclprinc=`%s'\n\tsvcprinc=`%s'\n", whoami, obuf, ((kret == 0) ? "success" : error_message(kret)), client_name, service_name); krb5_klog_syslog(LOG_NOTICE, _("Request: %s, %s, %s, client=%s, service=%s, addr=%s"), whoami, obuf, ((kret == 0) ? "success" : error_message(kret)), client_name, service_name, client_addr(rqstp->rq_xprt)); out: if (nofork) debprret(whoami, ret.ret, ret.lastentry.last_sno); free(client_name); free(service_name); return (&ret); } /* * Given a client princ (foo/fqdn@R), copy (in arg cl) the fqdn substring. * Return arg cl str ptr on success, else NULL. */ static char * getclhoststr(const char *clprinc, char *cl, size_t len) { const char *s, *e; if ((s = strchr(clprinc, '/')) == NULL || (e = strchr(++s, '@')) == NULL || (size_t)(e - s) >= len) return NULL; memcpy(cl, s, e - s); cl[e - s] = '\0'; return (cl); } static kdb_fullresync_result_t * ipropx_resync(uint32_t vers, struct svc_req *rqstp) { static kdb_fullresync_result_t ret; char *ubuf = 0; char clhost[MAXHOSTNAMELEN] = {0}; int pret, fret; FILE *p; kadm5_server_handle_t handle = global_server_handle; OM_uint32 min_stat; gss_name_t name = NULL; char *client_name = NULL, *service_name = NULL; char *whoami = "iprop_full_resync_1"; /* * vers contains the highest version number the client is * willing to accept. A client can always accept a lower * version: the version number is indicated in the dump * header. */ /* default return code */ ret.ret = UPDATE_ERROR; if (!handle) { krb5_klog_syslog(LOG_ERR, _("%s: server handle is NULL"), whoami); goto out; } DPRINT("%s: start\n", whoami); { gss_buffer_desc client_desc, service_desc; if (setup_gss_names(rqstp, &client_desc, &service_desc) < 0) { DPRINT("%s: setup_gss_names failed\n", whoami); krb5_klog_syslog(LOG_ERR, _("%s: setup_gss_names failed"), whoami); goto out; } client_name = buf_to_string(&client_desc); service_name = buf_to_string(&service_desc); if (client_name == NULL || service_name == NULL) { free(client_name); free(service_name); DPRINT("%s: out of memory\n", whoami); krb5_klog_syslog(LOG_ERR, _("%s: out of memory recording principal names"), whoami); goto out; } } DPRINT("%s: clprinc=`%s'\n\tsvcprinc=`%s'\n", whoami, client_name, service_name); if (!kadm5int_acl_check(handle->context, rqst2name(rqstp), ACL_IPROP, NULL, NULL)) { ret.ret = UPDATE_PERM_DENIED; DPRINT("%s: Permission denied\n", whoami); krb5_klog_syslog(LOG_NOTICE, LOG_UNAUTH, whoami, client_name, service_name, client_addr(rqstp->rq_xprt)); goto out; } if (!getclhoststr(client_name, clhost, sizeof (clhost))) { krb5_klog_syslog(LOG_ERR, _("%s: getclhoststr failed"), whoami); goto out; } /* * Note the -i; modified version of kdb5_util dump format * to include sno (serial number). This argument is now * versioned (-i0 for legacy dump format, -i1 for ipropx * version 1 format, etc). * * The -c option ("conditional") causes the dump to dump only if no * dump already exists or that dump is not in ipropx format, or the * sno and timestamp in the header of that dump are outside the * ulog. This allows us to share a single global dump with all * slaves, since it's OK to share an older dump, as long as its sno * and timestamp are in the ulog (then the slaves can get the * subsequent updates very iprop). */ if (asprintf(&ubuf, "%s dump -i%d -c %s", kdb5_util, vers, dump_file) < 0) { krb5_klog_syslog(LOG_ERR, _("%s: cannot construct kdb5 util dump string too long; out of memory"), whoami); goto out; } /* * Fork to dump the db and xfer it to the slave. * (the fork allows parent to return quickly and the child * acts like a callback to the slave). */ fret = fork(); DPRINT("%s: fork=%d (%d)\n", whoami, fret, getpid()); switch (fret) { case -1: /* error */ if (nofork) { perror(whoami); } DPRINT("%s: fork failed\n", whoami); krb5_klog_syslog(LOG_ERR, _("%s: fork failed: %s"), whoami, error_message(errno)); goto out; case 0: /* child */ DPRINT("%s: run `%s' ...\n", whoami, ubuf); (void) signal(SIGCHLD, SIG_DFL); /* run kdb5_util(1M) dump for IProp */ p = popen(ubuf, "w"); if (p == NULL) { krb5_klog_syslog(LOG_ERR, _("%s: popen failed: %s"), whoami, error_message(errno)); _exit(1); } pret = pclose(p); DPRINT("%s: pclose=%d\n", whoami, pret); if (pret != 0) { /* XXX popen/pclose may not set errno properly, and the error could be from the subprocess anyways. */ if (nofork) { perror(whoami); } krb5_klog_syslog(LOG_ERR, _("%s: pclose(popen) failed: %s"), whoami, error_message(errno)); _exit(1); } DPRINT("%s: exec `kprop -f %s %s' ...\n", whoami, dump_file, clhost); /* XXX Yuck! */ if (getenv("KPROP_PORT")) { pret = execl(kprop, "kprop", "-f", dump_file, "-P", getenv("KPROP_PORT"), clhost, NULL); } else { pret = execl(kprop, "kprop", "-f", dump_file, clhost, NULL); } perror(whoami); krb5_klog_syslog(LOG_ERR, _("%s: exec failed: %s"), whoami, error_message(errno)); _exit(1); default: /* parent */ ret.ret = UPDATE_OK; /* not used by slave (sno is retrieved from kdb5_util dump) */ ret.lastentry.last_sno = 0; ret.lastentry.last_time.seconds = 0; ret.lastentry.last_time.useconds = 0; DPRINT("%s: spawned resync process %d, client=%s, " "service=%s, addr=%s\n", whoami, fret, client_name, service_name, client_addr(rqstp->rq_xprt)); krb5_klog_syslog(LOG_NOTICE, _("Request: %s, spawned resync process %d, client=%s, service=%s, addr=%s"), whoami, fret, client_name, service_name, client_addr(rqstp->rq_xprt)); goto out; } out: if (nofork) debprret(whoami, ret.ret, 0); free(client_name); free(service_name); if (name) gss_release_name(&min_stat, &name); free(ubuf); return (&ret); } kdb_fullresync_result_t * iprop_full_resync_1_svc(/* LINTED */ void *argp, struct svc_req *rqstp) { return ipropx_resync(IPROPX_VERSION_0, rqstp); } kdb_fullresync_result_t * iprop_full_resync_ext_1_svc(uint32_t *argp, struct svc_req *rqstp) { return ipropx_resync(*argp, rqstp); } static int check_iprop_rpcsec_auth(struct svc_req *rqstp) { /* XXX Since the client can authenticate against any principal in the database, we need to do a sanity check. Only checking for "kiprop" now, but that means theoretically the client could be authenticating to kiprop on some other machine. */ /* Code taken from kadm_rpc_svc.c, tweaked. */ gss_ctx_id_t ctx; krb5_context kctx; OM_uint32 maj_stat, min_stat; gss_name_t name; krb5_principal princ; int ret, success; krb5_data *c1, *realm; gss_buffer_desc gss_str; kadm5_server_handle_t handle; size_t slen; char *sdots; success = 0; handle = (kadm5_server_handle_t)global_server_handle; if (rqstp->rq_cred.oa_flavor != RPCSEC_GSS) return 0; ctx = rqstp->rq_svccred; maj_stat = gss_inquire_context(&min_stat, ctx, NULL, &name, NULL, NULL, NULL, NULL, NULL); if (maj_stat != GSS_S_COMPLETE) { krb5_klog_syslog(LOG_ERR, _("check_rpcsec_auth: failed inquire_context, " "stat=%u"), maj_stat); log_badauth(maj_stat, min_stat, rqstp->rq_xprt, NULL); goto fail_name; } kctx = handle->context; ret = gss_to_krb5_name_1(rqstp, kctx, name, &princ, &gss_str); if (ret == 0) goto fail_name; slen = gss_str.length; trunc_name(&slen, &sdots); /* * Since we accept with GSS_C_NO_NAME, the client can authenticate * against the entire kdb. Therefore, ensure that the service * name is something reasonable. */ if (krb5_princ_size(kctx, princ) != 2) goto fail_princ; c1 = krb5_princ_component(kctx, princ, 0); realm = krb5_princ_realm(kctx, princ); if (strncmp(handle->params.realm, realm->data, realm->length) == 0 && strncmp("kiprop", c1->data, c1->length) == 0) { success = 1; } fail_princ: if (!success) { krb5_klog_syslog(LOG_ERR, _("bad service principal %.*s%s"), (int) slen, (char *) gss_str.value, sdots); } gss_release_buffer(&min_stat, &gss_str); krb5_free_principal(kctx, princ); fail_name: gss_release_name(&min_stat, &name); return success; } void krb5_iprop_prog_1(struct svc_req *rqstp, register SVCXPRT *transp) { union { kdb_last_t iprop_get_updates_1_arg; } argument; char *result; bool_t (*_xdr_argument)(), (*_xdr_result)(); char *(*local)(/* union XXX *, struct svc_req * */); char *whoami = "krb5_iprop_prog_1"; if (!check_iprop_rpcsec_auth(rqstp)) { krb5_klog_syslog(LOG_ERR, _("authentication attempt failed: %s, RPC " "authentication flavor %d"), client_addr(rqstp->rq_xprt), rqstp->rq_cred.oa_flavor); svcerr_weakauth(transp); return; } switch (rqstp->rq_proc) { case NULLPROC: (void) svc_sendreply(transp, xdr_void, (char *)NULL); return; case IPROP_GET_UPDATES: _xdr_argument = xdr_kdb_last_t; _xdr_result = xdr_kdb_incr_result_t; local = (char *(*)()) iprop_get_updates_1_svc; break; case IPROP_FULL_RESYNC: _xdr_argument = xdr_void; _xdr_result = xdr_kdb_fullresync_result_t; local = (char *(*)()) iprop_full_resync_1_svc; break; case IPROP_FULL_RESYNC_EXT: _xdr_argument = xdr_u_int32; _xdr_result = xdr_kdb_fullresync_result_t; local = (char *(*)()) iprop_full_resync_ext_1_svc; break; default: krb5_klog_syslog(LOG_ERR, _("RPC unknown request: %d (%s)"), rqstp->rq_proc, whoami); svcerr_noproc(transp); return; } (void) memset(&argument, 0, sizeof (argument)); if (!svc_getargs(transp, _xdr_argument, (caddr_t)&argument)) { krb5_klog_syslog(LOG_ERR, _("RPC svc_getargs failed (%s)"), whoami); svcerr_decode(transp); return; } result = (*local)(&argument, rqstp); if (_xdr_result && result != NULL && !svc_sendreply(transp, _xdr_result, result)) { krb5_klog_syslog(LOG_ERR, _("RPC svc_sendreply failed (%s)"), whoami); svcerr_systemerr(transp); } if (!svc_freeargs(transp, _xdr_argument, (caddr_t)&argument)) { krb5_klog_syslog(LOG_ERR, _("RPC svc_freeargs failed (%s)"), whoami); exit(1); } if (rqstp->rq_proc == IPROP_GET_UPDATES) { /* LINTED */ kdb_incr_result_t *r = (kdb_incr_result_t *)result; if (r->ret == UPDATE_OK) { ulog_free_entries(r->updates.kdb_ulog_t_val, r->updates.kdb_ulog_t_len); r->updates.kdb_ulog_t_val = NULL; r->updates.kdb_ulog_t_len = 0; } } } #if 0 /* * Get the host base service name for the kiprop principal. Returns * KADM5_OK on success. Caller must free the storage allocated for * host_service_name. */ kadm5_ret_t kiprop_get_adm_host_srv_name(krb5_context context, const char *realm, char **host_service_name) { kadm5_ret_t ret; char *name; char *host; if (ret = kadm5_get_master(context, realm, &host)) return (ret); if (asprintf(&name, "%s@%s", KIPROP_SVC_NAME, host) < 0) { free(host); return (ENOMEM); } free(host); *host_service_name = name; return (KADM5_OK); } #endif