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