2 * Copyright (c) 2008 Doug Rabson
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 Copyright (c) 2000 The Regents of the University of Michigan.
32 Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
33 All rights reserved, all wrongs reversed.
35 Redistribution and use in source and binary forms, with or without
36 modification, are permitted provided that the following conditions
39 1. Redistributions of source code must retain the above copyright
40 notice, this list of conditions and the following disclaimer.
41 2. Redistributions in binary form must reproduce the above copyright
42 notice, this list of conditions and the following disclaimer in the
43 documentation and/or other materials provided with the distribution.
44 3. Neither the name of the University nor the names of its
45 contributors may be used to endorse or promote products derived
46 from this software without specific prior written permission.
48 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
49 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
50 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
51 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
52 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
53 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
54 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
55 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
56 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
57 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
58 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
60 $Id: svc_auth_gss.c,v 1.27 2002/01/15 15:43:00 andros Exp $
63 #include <sys/cdefs.h>
64 __FBSDID("$FreeBSD$");
66 #include <sys/param.h>
67 #include <sys/systm.h>
69 #include <sys/kernel.h>
72 #include <sys/malloc.h>
74 #include <sys/mutex.h>
77 #include <sys/ucred.h>
80 #include <rpc/rpcsec_gss.h>
82 #include "rpcsec_gss_int.h"
84 static bool_t svc_rpc_gss_wrap(SVCAUTH *, struct mbuf **);
85 static bool_t svc_rpc_gss_unwrap(SVCAUTH *, struct mbuf **);
86 static void svc_rpc_gss_release(SVCAUTH *);
87 static enum auth_stat svc_rpc_gss(struct svc_req *, struct rpc_msg *);
88 static int rpc_gss_svc_getcred(struct svc_req *, struct ucred **, int *);
90 static struct svc_auth_ops svc_auth_gss_ops = {
96 struct sx svc_rpc_gss_lock;
98 struct svc_rpc_gss_callback {
99 SLIST_ENTRY(svc_rpc_gss_callback) cb_link;
100 rpc_gss_callback_t cb_callback;
102 static SLIST_HEAD(svc_rpc_gss_callback_list, svc_rpc_gss_callback)
103 svc_rpc_gss_callbacks = SLIST_HEAD_INITIALIZER(svc_rpc_gss_callbacks);
105 struct svc_rpc_gss_svc_name {
106 SLIST_ENTRY(svc_rpc_gss_svc_name) sn_link;
110 gss_cred_id_t sn_cred;
114 static SLIST_HEAD(svc_rpc_gss_svc_name_list, svc_rpc_gss_svc_name)
115 svc_rpc_gss_svc_names = SLIST_HEAD_INITIALIZER(svc_rpc_gss_svc_names);
117 enum svc_rpc_gss_client_state {
118 CLIENT_NEW, /* still authenticating */
119 CLIENT_ESTABLISHED, /* context established */
120 CLIENT_STALE /* garbage to collect */
123 #define SVC_RPC_GSS_SEQWINDOW 128
125 struct svc_rpc_gss_clientid {
126 unsigned long ci_hostid;
127 uint32_t ci_boottime;
131 struct svc_rpc_gss_client {
132 TAILQ_ENTRY(svc_rpc_gss_client) cl_link;
133 TAILQ_ENTRY(svc_rpc_gss_client) cl_alllink;
134 volatile u_int cl_refs;
136 struct svc_rpc_gss_clientid cl_id;
137 time_t cl_expiration; /* when to gc */
138 enum svc_rpc_gss_client_state cl_state; /* client state */
139 bool_t cl_locked; /* fixed service+qop */
140 gss_ctx_id_t cl_ctx; /* context id */
141 gss_cred_id_t cl_creds; /* delegated creds */
142 gss_name_t cl_cname; /* client name */
143 struct svc_rpc_gss_svc_name *cl_sname; /* server name used */
144 rpc_gss_rawcred_t cl_rawcred; /* raw credentials */
145 rpc_gss_ucred_t cl_ucred; /* unix-style credentials */
146 struct ucred *cl_cred; /* kernel-style credentials */
147 int cl_rpcflavor; /* RPC pseudo sec flavor */
148 bool_t cl_done_callback; /* TRUE after call */
149 void *cl_cookie; /* user cookie from callback */
150 gid_t cl_gid_storage[NGROUPS];
151 gss_OID cl_mech; /* mechanism */
152 gss_qop_t cl_qop; /* quality of protection */
153 uint32_t cl_seqlast; /* sequence window origin */
154 uint32_t cl_seqmask[SVC_RPC_GSS_SEQWINDOW/32]; /* bitmask of seqnums */
156 TAILQ_HEAD(svc_rpc_gss_client_list, svc_rpc_gss_client);
159 * This structure holds enough information to unwrap arguments or wrap
160 * results for a given request. We use the rq_clntcred area for this
161 * (which is a per-request buffer).
163 struct svc_rpc_gss_cookedcred {
164 struct svc_rpc_gss_client *cc_client;
165 rpc_gss_service_t cc_service;
169 #define CLIENT_HASH_SIZE 256
170 #define CLIENT_MAX 128
171 struct svc_rpc_gss_client_list svc_rpc_gss_client_hash[CLIENT_HASH_SIZE];
172 struct svc_rpc_gss_client_list svc_rpc_gss_clients;
173 static size_t svc_rpc_gss_client_count;
174 static uint32_t svc_rpc_gss_next_clientid = 1;
177 svc_rpc_gss_init(void *arg)
181 for (i = 0; i < CLIENT_HASH_SIZE; i++)
182 TAILQ_INIT(&svc_rpc_gss_client_hash[i]);
183 TAILQ_INIT(&svc_rpc_gss_clients);
184 svc_auth_reg(RPCSEC_GSS, svc_rpc_gss, rpc_gss_svc_getcred);
185 sx_init(&svc_rpc_gss_lock, "gsslock");
187 SYSINIT(svc_rpc_gss_init, SI_SUB_KMEM, SI_ORDER_ANY, svc_rpc_gss_init, NULL);
190 rpc_gss_set_callback(rpc_gss_callback_t *cb)
192 struct svc_rpc_gss_callback *scb;
194 scb = mem_alloc(sizeof(struct svc_rpc_gss_callback));
196 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
199 scb->cb_callback = *cb;
200 sx_xlock(&svc_rpc_gss_lock);
201 SLIST_INSERT_HEAD(&svc_rpc_gss_callbacks, scb, cb_link);
202 sx_xunlock(&svc_rpc_gss_lock);
208 rpc_gss_clear_callback(rpc_gss_callback_t *cb)
210 struct svc_rpc_gss_callback *scb;
212 sx_xlock(&svc_rpc_gss_lock);
213 SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
214 if (scb->cb_callback.program == cb->program
215 && scb->cb_callback.version == cb->version
216 && scb->cb_callback.callback == cb->callback) {
217 SLIST_REMOVE(&svc_rpc_gss_callbacks, scb,
218 svc_rpc_gss_callback, cb_link);
219 sx_xunlock(&svc_rpc_gss_lock);
220 mem_free(scb, sizeof(*scb));
224 sx_xunlock(&svc_rpc_gss_lock);
228 rpc_gss_acquire_svc_cred(struct svc_rpc_gss_svc_name *sname)
230 OM_uint32 maj_stat, min_stat;
231 gss_buffer_desc namebuf;
233 gss_OID_set_desc oid_set;
236 oid_set.elements = sname->sn_mech;
238 namebuf.value = (void *) sname->sn_principal;
239 namebuf.length = strlen(sname->sn_principal);
241 maj_stat = gss_import_name(&min_stat, &namebuf,
242 GSS_C_NT_HOSTBASED_SERVICE, &name);
243 if (maj_stat != GSS_S_COMPLETE)
246 if (sname->sn_cred != GSS_C_NO_CREDENTIAL)
247 gss_release_cred(&min_stat, &sname->sn_cred);
249 maj_stat = gss_acquire_cred(&min_stat, name,
250 sname->sn_req_time, &oid_set, GSS_C_ACCEPT, &sname->sn_cred,
252 if (maj_stat != GSS_S_COMPLETE) {
253 gss_release_name(&min_stat, &name);
256 gss_release_name(&min_stat, &name);
262 rpc_gss_set_svc_name(const char *principal, const char *mechanism,
263 u_int req_time, u_int program, u_int version)
265 struct svc_rpc_gss_svc_name *sname;
268 if (!rpc_gss_mech_to_oid(mechanism, &mech_oid))
271 sname = mem_alloc(sizeof(*sname));
274 sname->sn_principal = strdup(principal, M_RPC);
275 sname->sn_mech = mech_oid;
276 sname->sn_req_time = req_time;
277 sname->sn_cred = GSS_C_NO_CREDENTIAL;
278 sname->sn_program = program;
279 sname->sn_version = version;
281 if (!rpc_gss_acquire_svc_cred(sname)) {
282 free(sname->sn_principal, M_RPC);
283 mem_free(sname, sizeof(*sname));
287 sx_xlock(&svc_rpc_gss_lock);
288 SLIST_INSERT_HEAD(&svc_rpc_gss_svc_names, sname, sn_link);
289 sx_xunlock(&svc_rpc_gss_lock);
295 rpc_gss_clear_svc_name(u_int program, u_int version)
298 struct svc_rpc_gss_svc_name *sname;
300 sx_xlock(&svc_rpc_gss_lock);
301 SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
302 if (sname->sn_program == program
303 && sname->sn_version == version) {
304 SLIST_REMOVE(&svc_rpc_gss_svc_names, sname,
305 svc_rpc_gss_svc_name, sn_link);
306 sx_xunlock(&svc_rpc_gss_lock);
307 gss_release_cred(&min_stat, &sname->sn_cred);
308 free(sname->sn_principal, M_RPC);
309 mem_free(sname, sizeof(*sname));
313 sx_xunlock(&svc_rpc_gss_lock);
317 rpc_gss_get_principal_name(rpc_gss_principal_t *principal,
318 const char *mech, const char *name, const char *node, const char *domain)
320 OM_uint32 maj_stat, min_stat;
324 gss_name_t gss_name, gss_mech_name;
325 rpc_gss_principal_t result;
327 if (!rpc_gss_mech_to_oid(mech, &mech_oid))
331 * Construct a gss_buffer containing the full name formatted
332 * as "name/node@domain" where node and domain are optional.
334 namelen = strlen(name);
336 namelen += strlen(node) + 1;
339 namelen += strlen(domain) + 1;
342 buf.value = mem_alloc(namelen);
343 buf.length = namelen;
344 strcpy((char *) buf.value, name);
346 strcat((char *) buf.value, "/");
347 strcat((char *) buf.value, node);
350 strcat((char *) buf.value, "@");
351 strcat((char *) buf.value, domain);
355 * Convert that to a gss_name_t and then convert that to a
356 * mechanism name in the selected mechanism.
358 maj_stat = gss_import_name(&min_stat, &buf,
359 GSS_C_NT_USER_NAME, &gss_name);
360 mem_free(buf.value, buf.length);
361 if (maj_stat != GSS_S_COMPLETE) {
362 rpc_gss_log_status("gss_import_name", mech_oid, maj_stat, min_stat);
365 maj_stat = gss_canonicalize_name(&min_stat, gss_name, mech_oid,
367 if (maj_stat != GSS_S_COMPLETE) {
368 rpc_gss_log_status("gss_canonicalize_name", mech_oid, maj_stat,
370 gss_release_name(&min_stat, &gss_name);
373 gss_release_name(&min_stat, &gss_name);
376 * Export the mechanism name and use that to construct the
377 * rpc_gss_principal_t result.
379 maj_stat = gss_export_name(&min_stat, gss_mech_name, &buf);
380 if (maj_stat != GSS_S_COMPLETE) {
381 rpc_gss_log_status("gss_export_name", mech_oid, maj_stat, min_stat);
382 gss_release_name(&min_stat, &gss_mech_name);
385 gss_release_name(&min_stat, &gss_mech_name);
387 result = mem_alloc(sizeof(int) + buf.length);
389 gss_release_buffer(&min_stat, &buf);
392 result->len = buf.length;
393 memcpy(result->name, buf.value, buf.length);
394 gss_release_buffer(&min_stat, &buf);
401 rpc_gss_getcred(struct svc_req *req, rpc_gss_rawcred_t **rcred,
402 rpc_gss_ucred_t **ucred, void **cookie)
404 struct svc_rpc_gss_cookedcred *cc;
405 struct svc_rpc_gss_client *client;
407 if (req->rq_cred.oa_flavor != RPCSEC_GSS)
410 cc = req->rq_clntcred;
411 client = cc->cc_client;
413 *rcred = &client->cl_rawcred;
415 *ucred = &client->cl_ucred;
417 *cookie = client->cl_cookie;
422 * This simpler interface is used by svc_getcred to copy the cred data
423 * into a kernel cred structure.
426 rpc_gss_svc_getcred(struct svc_req *req, struct ucred **crp, int *flavorp)
429 struct svc_rpc_gss_cookedcred *cc;
430 struct svc_rpc_gss_client *client;
433 if (req->rq_cred.oa_flavor != RPCSEC_GSS)
436 cc = req->rq_clntcred;
437 client = cc->cc_client;
440 *flavorp = client->cl_rpcflavor;
442 if (client->cl_cred) {
443 *crp = crhold(client->cl_cred);
447 uc = &client->cl_ucred;
448 cr = client->cl_cred = crget();
449 cr->cr_uid = cr->cr_ruid = cr->cr_svuid = uc->uid;
450 cr->cr_rgid = cr->cr_svgid = uc->gid;
451 crsetgroups(cr, uc->gidlen, uc->gidlist);
452 cr->cr_prison = &prison0;
453 prison_hold(cr->cr_prison);
460 rpc_gss_svc_max_data_length(struct svc_req *req, int max_tp_unit_len)
462 struct svc_rpc_gss_cookedcred *cc = req->rq_clntcred;
463 struct svc_rpc_gss_client *client = cc->cc_client;
466 OM_uint32 maj_stat, min_stat;
469 switch (client->cl_rawcred.service) {
470 case rpc_gss_svc_none:
471 return (max_tp_unit_len);
474 case rpc_gss_svc_default:
475 case rpc_gss_svc_integrity:
479 case rpc_gss_svc_privacy:
487 maj_stat = gss_wrap_size_limit(&min_stat, client->cl_ctx, want_conf,
488 client->cl_qop, max_tp_unit_len, &max);
490 if (maj_stat == GSS_S_COMPLETE) {
496 rpc_gss_log_status("gss_wrap_size_limit", client->cl_mech,
502 static struct svc_rpc_gss_client *
503 svc_rpc_gss_find_client(struct svc_rpc_gss_clientid *id)
505 struct svc_rpc_gss_client *client;
506 struct svc_rpc_gss_client_list *list;
507 unsigned long hostid;
509 rpc_gss_log_debug("in svc_rpc_gss_find_client(%d)", id->ci_id);
511 getcredhostid(curthread->td_ucred, &hostid);
512 if (id->ci_hostid != hostid || id->ci_boottime != boottime.tv_sec)
515 list = &svc_rpc_gss_client_hash[id->ci_id % CLIENT_HASH_SIZE];
516 sx_xlock(&svc_rpc_gss_lock);
517 TAILQ_FOREACH(client, list, cl_link) {
518 if (client->cl_id.ci_id == id->ci_id) {
520 * Move this client to the front of the LRU
523 TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
524 TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client,
526 refcount_acquire(&client->cl_refs);
530 sx_xunlock(&svc_rpc_gss_lock);
535 static struct svc_rpc_gss_client *
536 svc_rpc_gss_create_client(void)
538 struct svc_rpc_gss_client *client;
539 struct svc_rpc_gss_client_list *list;
540 unsigned long hostid;
542 rpc_gss_log_debug("in svc_rpc_gss_create_client()");
544 client = mem_alloc(sizeof(struct svc_rpc_gss_client));
545 memset(client, 0, sizeof(struct svc_rpc_gss_client));
546 refcount_init(&client->cl_refs, 1);
547 sx_init(&client->cl_lock, "GSS-client");
548 getcredhostid(curthread->td_ucred, &hostid);
549 client->cl_id.ci_hostid = hostid;
550 client->cl_id.ci_boottime = boottime.tv_sec;
551 client->cl_id.ci_id = svc_rpc_gss_next_clientid++;
552 list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
553 sx_xlock(&svc_rpc_gss_lock);
554 TAILQ_INSERT_HEAD(list, client, cl_link);
555 TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client, cl_alllink);
556 svc_rpc_gss_client_count++;
557 sx_xunlock(&svc_rpc_gss_lock);
560 * Start the client off with a short expiration time. We will
561 * try to get a saner value from the client creds later.
563 client->cl_state = CLIENT_NEW;
564 client->cl_locked = FALSE;
565 client->cl_expiration = time_uptime + 5*60;
571 svc_rpc_gss_destroy_client(struct svc_rpc_gss_client *client)
575 rpc_gss_log_debug("in svc_rpc_gss_destroy_client()");
578 gss_delete_sec_context(&min_stat,
579 &client->cl_ctx, GSS_C_NO_BUFFER);
581 if (client->cl_cname)
582 gss_release_name(&min_stat, &client->cl_cname);
584 if (client->cl_rawcred.client_principal)
585 mem_free(client->cl_rawcred.client_principal,
586 sizeof(*client->cl_rawcred.client_principal)
587 + client->cl_rawcred.client_principal->len);
590 crfree(client->cl_cred);
592 sx_destroy(&client->cl_lock);
593 mem_free(client, sizeof(*client));
597 * Drop a reference to a client and free it if that was the last reference.
600 svc_rpc_gss_release_client(struct svc_rpc_gss_client *client)
603 if (!refcount_release(&client->cl_refs))
605 svc_rpc_gss_destroy_client(client);
609 * Remove a client from our global lists.
610 * Must be called with svc_rpc_gss_lock held.
613 svc_rpc_gss_forget_client_locked(struct svc_rpc_gss_client *client)
615 struct svc_rpc_gss_client_list *list;
617 sx_assert(&svc_rpc_gss_lock, SX_XLOCKED);
618 list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
619 TAILQ_REMOVE(list, client, cl_link);
620 TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
621 svc_rpc_gss_client_count--;
625 * Remove a client from our global lists and free it if we can.
628 svc_rpc_gss_forget_client(struct svc_rpc_gss_client *client)
630 struct svc_rpc_gss_client_list *list;
631 struct svc_rpc_gss_client *tclient;
633 list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
634 sx_xlock(&svc_rpc_gss_lock);
635 TAILQ_FOREACH(tclient, list, cl_link) {
637 * Make sure this client has not already been removed
638 * from the lists by svc_rpc_gss_forget_client() or
639 * svc_rpc_gss_forget_client_locked().
641 if (client == tclient) {
642 svc_rpc_gss_forget_client_locked(client);
643 sx_xunlock(&svc_rpc_gss_lock);
644 svc_rpc_gss_release_client(client);
648 sx_xunlock(&svc_rpc_gss_lock);
652 svc_rpc_gss_timeout_clients(void)
654 struct svc_rpc_gss_client *client;
655 time_t now = time_uptime;
657 rpc_gss_log_debug("in svc_rpc_gss_timeout_clients()");
660 * First enforce the max client limit. We keep
661 * svc_rpc_gss_clients in LRU order.
663 sx_xlock(&svc_rpc_gss_lock);
664 client = TAILQ_LAST(&svc_rpc_gss_clients, svc_rpc_gss_client_list);
665 while (svc_rpc_gss_client_count > CLIENT_MAX && client != NULL) {
666 svc_rpc_gss_forget_client_locked(client);
667 sx_xunlock(&svc_rpc_gss_lock);
668 svc_rpc_gss_release_client(client);
669 sx_xlock(&svc_rpc_gss_lock);
670 client = TAILQ_LAST(&svc_rpc_gss_clients,
671 svc_rpc_gss_client_list);
674 TAILQ_FOREACH(client, &svc_rpc_gss_clients, cl_alllink) {
675 if (client->cl_state == CLIENT_STALE
676 || now > client->cl_expiration) {
677 svc_rpc_gss_forget_client_locked(client);
678 sx_xunlock(&svc_rpc_gss_lock);
679 rpc_gss_log_debug("expiring client %p", client);
680 svc_rpc_gss_release_client(client);
681 sx_xlock(&svc_rpc_gss_lock);
685 sx_xunlock(&svc_rpc_gss_lock);
690 * OID<->string routines. These are uuuuugly.
693 gss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
696 unsigned long number;
698 size_t string_length;
703 /* Decoded according to krb5/gssapi_krb5.c */
705 /* First determine the size of the string */
709 cp = (unsigned char *) oid->elements;
710 number = (unsigned long) cp[0];
711 sprintf(numstr, "%ld ", number/40);
712 string_length += strlen(numstr);
713 sprintf(numstr, "%ld ", number%40);
714 string_length += strlen(numstr);
715 for (i=1; i<oid->length; i++) {
716 if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
717 number = (number << 7) | (cp[i] & 0x7f);
722 return(GSS_S_FAILURE);
724 if ((cp[i] & 0x80) == 0) {
725 sprintf(numstr, "%ld ", number);
726 string_length += strlen(numstr);
732 * If we get here, we've calculated the length of "n n n ... n ". Add 4
733 * here for "{ " and "}\0".
736 if ((bp = (char *) mem_alloc(string_length))) {
738 number = (unsigned long) cp[0];
739 sprintf(numstr, "%ld ", number/40);
741 sprintf(numstr, "%ld ", number%40);
744 cp = (unsigned char *) oid->elements;
745 for (i=1; i<oid->length; i++) {
746 number = (number << 7) | (cp[i] & 0x7f);
747 if ((cp[i] & 0x80) == 0) {
748 sprintf(numstr, "%ld ", number);
754 oid_str->length = strlen(bp)+1;
755 oid_str->value = (void *) bp;
757 return(GSS_S_COMPLETE);
760 return(GSS_S_FAILURE);
765 svc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
766 const gss_name_t name)
768 OM_uint32 maj_stat, min_stat;
769 rpc_gss_ucred_t *uc = &client->cl_ucred;
774 uc->gidlist = client->cl_gid_storage;
777 maj_stat = gss_pname_to_unix_cred(&min_stat, name, client->cl_mech,
778 &uc->uid, &uc->gid, &numgroups, &uc->gidlist[0]);
779 if (GSS_ERROR(maj_stat))
782 uc->gidlen = numgroups;
786 svc_rpc_gss_set_flavor(struct svc_rpc_gss_client *client)
788 static gss_OID_desc krb5_mech_oid =
789 {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
792 * Attempt to translate mech type and service into a
793 * 'pseudo flavor'. Hardwire in krb5 support for now.
795 if (kgss_oid_equal(client->cl_mech, &krb5_mech_oid)) {
796 switch (client->cl_rawcred.service) {
797 case rpc_gss_svc_default:
798 case rpc_gss_svc_none:
799 client->cl_rpcflavor = RPCSEC_GSS_KRB5;
801 case rpc_gss_svc_integrity:
802 client->cl_rpcflavor = RPCSEC_GSS_KRB5I;
804 case rpc_gss_svc_privacy:
805 client->cl_rpcflavor = RPCSEC_GSS_KRB5P;
809 client->cl_rpcflavor = RPCSEC_GSS;
814 svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
815 struct svc_req *rqst,
816 struct rpc_gss_init_res *gr,
817 struct rpc_gss_cred *gc)
819 gss_buffer_desc recv_tok;
821 OM_uint32 maj_stat = 0, min_stat = 0, ret_flags;
822 OM_uint32 cred_lifetime;
823 struct svc_rpc_gss_svc_name *sname;
825 rpc_gss_log_debug("in svc_rpc_gss_accept_context()");
827 /* Deserialize arguments. */
828 memset(&recv_tok, 0, sizeof(recv_tok));
830 if (!svc_getargs(rqst,
831 (xdrproc_t) xdr_gss_buffer_desc,
832 (caddr_t) &recv_tok)) {
833 client->cl_state = CLIENT_STALE;
838 * First time round, try all the server names we have until
839 * one matches. Afterwards, stick with that one.
841 sx_xlock(&svc_rpc_gss_lock);
842 if (!client->cl_sname) {
843 SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
844 if (sname->sn_program == rqst->rq_prog
845 && sname->sn_version == rqst->rq_vers) {
847 gr->gr_major = gss_accept_sec_context(
852 GSS_C_NO_CHANNEL_BINDINGS,
860 GSS_S_CREDENTIALS_EXPIRED) {
862 * Either our creds really did
866 if (rpc_gss_acquire_svc_cred(sname))
869 client->cl_sname = sname;
874 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
876 sx_xunlock(&svc_rpc_gss_lock);
880 gr->gr_major = gss_accept_sec_context(
883 client->cl_sname->sn_cred,
885 GSS_C_NO_CHANNEL_BINDINGS,
893 sx_xunlock(&svc_rpc_gss_lock);
895 xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
898 * If we get an error from gss_accept_sec_context, send the
899 * reply anyway so that the client gets a chance to see what
902 if (gr->gr_major != GSS_S_COMPLETE &&
903 gr->gr_major != GSS_S_CONTINUE_NEEDED) {
904 rpc_gss_log_status("accept_sec_context", client->cl_mech,
905 gr->gr_major, gr->gr_minor);
906 client->cl_state = CLIENT_STALE;
910 gr->gr_handle.value = &client->cl_id;
911 gr->gr_handle.length = sizeof(client->cl_id);
912 gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
914 /* Save client info. */
915 client->cl_mech = mech;
916 client->cl_qop = GSS_C_QOP_DEFAULT;
917 client->cl_done_callback = FALSE;
919 if (gr->gr_major == GSS_S_COMPLETE) {
920 gss_buffer_desc export_name;
923 * Change client expiration time to be near when the
924 * client creds expire (or 24 hours if we can't figure
927 if (cred_lifetime == GSS_C_INDEFINITE)
928 cred_lifetime = time_uptime + 24*60*60;
930 client->cl_expiration = time_uptime + cred_lifetime;
933 * Fill in cred details in the rawcred structure.
935 client->cl_rawcred.version = RPCSEC_GSS_VERSION;
936 rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
937 maj_stat = gss_export_name(&min_stat, client->cl_cname,
939 if (maj_stat != GSS_S_COMPLETE) {
940 rpc_gss_log_status("gss_export_name", client->cl_mech,
944 client->cl_rawcred.client_principal =
945 mem_alloc(sizeof(*client->cl_rawcred.client_principal)
946 + export_name.length);
947 client->cl_rawcred.client_principal->len = export_name.length;
948 memcpy(client->cl_rawcred.client_principal->name,
949 export_name.value, export_name.length);
950 gss_release_buffer(&min_stat, &export_name);
951 client->cl_rawcred.svc_principal =
952 client->cl_sname->sn_principal;
953 client->cl_rawcred.service = gc->gc_svc;
956 * Use gss_pname_to_uid to map to unix creds. For
957 * kerberos5, this uses krb5_aname_to_localname.
959 svc_rpc_gss_build_ucred(client, client->cl_cname);
960 svc_rpc_gss_set_flavor(client);
961 gss_release_name(&min_stat, &client->cl_cname);
965 gss_buffer_desc mechname;
967 gss_oid_to_str(&min_stat, mech, &mechname);
969 rpc_gss_log_debug("accepted context for %s with "
970 "<mech %.*s, qop %d, svc %d>",
971 client->cl_rawcred.client_principal->name,
972 mechname.length, (char *)mechname.value,
973 client->cl_qop, client->cl_rawcred.service);
975 gss_release_buffer(&min_stat, &mechname);
983 svc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
984 gss_qop_t *qop, rpc_gss_proc_t gcproc)
986 struct opaque_auth *oa;
987 gss_buffer_desc rpcbuf, checksum;
988 OM_uint32 maj_stat, min_stat;
990 int32_t rpchdr[128 / sizeof(int32_t)];
993 rpc_gss_log_debug("in svc_rpc_gss_validate()");
995 memset(rpchdr, 0, sizeof(rpchdr));
997 /* Reconstruct RPC header for signing (from xdr_callmsg). */
999 IXDR_PUT_LONG(buf, msg->rm_xid);
1000 IXDR_PUT_ENUM(buf, msg->rm_direction);
1001 IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
1002 IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
1003 IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
1004 IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
1005 oa = &msg->rm_call.cb_cred;
1006 IXDR_PUT_ENUM(buf, oa->oa_flavor);
1007 IXDR_PUT_LONG(buf, oa->oa_length);
1008 if (oa->oa_length) {
1009 memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
1010 buf += RNDUP(oa->oa_length) / sizeof(int32_t);
1012 rpcbuf.value = rpchdr;
1013 rpcbuf.length = (u_char *)buf - (u_char *)rpchdr;
1015 checksum.value = msg->rm_call.cb_verf.oa_base;
1016 checksum.length = msg->rm_call.cb_verf.oa_length;
1018 maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
1021 if (maj_stat != GSS_S_COMPLETE) {
1022 rpc_gss_log_status("gss_verify_mic", client->cl_mech,
1023 maj_stat, min_stat);
1025 * A bug in some versions of the Linux client generates a
1026 * Destroy operation with a bogus encrypted checksum. Deleting
1027 * the credential handle for that case causes the mount to fail.
1028 * Since the checksum is bogus (gss_verify_mic() failed), it
1029 * doesn't make sense to destroy the handle and not doing so
1030 * fixes the Linux mount.
1032 if (gcproc != RPCSEC_GSS_DESTROY)
1033 client->cl_state = CLIENT_STALE;
1042 svc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
1043 struct svc_req *rqst, u_int seq)
1045 gss_buffer_desc signbuf;
1046 gss_buffer_desc mic;
1047 OM_uint32 maj_stat, min_stat;
1050 rpc_gss_log_debug("in svc_rpc_gss_nextverf()");
1053 signbuf.value = &nseq;
1054 signbuf.length = sizeof(nseq);
1056 maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
1059 if (maj_stat != GSS_S_COMPLETE) {
1060 rpc_gss_log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
1061 client->cl_state = CLIENT_STALE;
1065 KASSERT(mic.length <= MAX_AUTH_BYTES,
1066 ("MIC too large for RPCSEC_GSS"));
1068 rqst->rq_verf.oa_flavor = RPCSEC_GSS;
1069 rqst->rq_verf.oa_length = mic.length;
1070 bcopy(mic.value, rqst->rq_verf.oa_base, mic.length);
1072 gss_release_buffer(&min_stat, &mic);
1078 svc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
1080 struct svc_rpc_gss_callback *scb;
1081 rpc_gss_lock_t lock;
1087 * See if we have a callback for this guy.
1090 SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
1091 if (scb->cb_callback.program == rqst->rq_prog
1092 && scb->cb_callback.version == rqst->rq_vers) {
1094 * This one matches. Call the callback and see
1095 * if it wants to veto or something.
1097 lock.locked = FALSE;
1098 lock.raw_cred = &client->cl_rawcred;
1099 cb_res = scb->cb_callback.callback(rqst,
1106 client->cl_state = CLIENT_STALE;
1112 * The callback accepted the connection - it
1113 * is responsible for freeing client->cl_creds
1116 client->cl_creds = GSS_C_NO_CREDENTIAL;
1117 client->cl_locked = lock.locked;
1118 client->cl_cookie = cookie;
1124 * Either no callback exists for this program/version or one
1125 * of the callbacks rejected the connection. We just need to
1126 * clean up the delegated client creds, if any.
1128 if (client->cl_creds) {
1130 gss_release_cred(&min_ver, &client->cl_creds);
1136 svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
1142 sx_xlock(&client->cl_lock);
1143 if (seq <= client->cl_seqlast) {
1145 * The request sequence number is less than
1146 * the largest we have seen so far. If it is
1147 * outside the window or if we have seen a
1148 * request with this sequence before, silently
1151 offset = client->cl_seqlast - seq;
1152 if (offset >= SVC_RPC_GSS_SEQWINDOW) {
1158 if (client->cl_seqmask[word] & (1 << bit)) {
1166 sx_xunlock(&client->cl_lock);
1171 svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
1173 int offset, i, word, bit;
1174 uint32_t carry, newcarry;
1176 sx_xlock(&client->cl_lock);
1177 if (seq > client->cl_seqlast) {
1179 * This request has a sequence number greater
1180 * than any we have seen so far. Advance the
1181 * seq window and set bit zero of the window
1182 * (which corresponds to the new sequence
1185 offset = seq - client->cl_seqlast;
1186 while (offset > 32) {
1187 for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
1189 client->cl_seqmask[i] = client->cl_seqmask[i-1];
1191 client->cl_seqmask[0] = 0;
1195 for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
1196 newcarry = client->cl_seqmask[i] >> (32 - offset);
1197 client->cl_seqmask[i] =
1198 (client->cl_seqmask[i] << offset) | carry;
1201 client->cl_seqmask[0] |= 1;
1202 client->cl_seqlast = seq;
1204 offset = client->cl_seqlast - seq;
1207 client->cl_seqmask[word] |= (1 << bit);
1209 sx_xunlock(&client->cl_lock);
1213 svc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
1218 struct svc_rpc_gss_cookedcred *cc;
1219 struct svc_rpc_gss_client *client;
1220 struct rpc_gss_cred gc;
1221 struct rpc_gss_init_res gr;
1224 enum auth_stat result;
1226 rpc_gss_log_debug("in svc_rpc_gss()");
1228 /* Garbage collect old clients. */
1229 svc_rpc_gss_timeout_clients();
1231 /* Initialize reply. */
1232 rqst->rq_verf = _null_auth;
1234 /* Deserialize client credentials. */
1235 if (rqst->rq_cred.oa_length <= 0)
1236 return (AUTH_BADCRED);
1238 memset(&gc, 0, sizeof(gc));
1240 xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
1241 rqst->rq_cred.oa_length, XDR_DECODE);
1243 if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
1245 return (AUTH_BADCRED);
1251 /* Check version. */
1252 if (gc.gc_version != RPCSEC_GSS_VERSION) {
1253 result = AUTH_BADCRED;
1257 /* Check the proc and find the client (or create it) */
1258 if (gc.gc_proc == RPCSEC_GSS_INIT) {
1259 if (gc.gc_handle.length != 0) {
1260 result = AUTH_BADCRED;
1263 client = svc_rpc_gss_create_client();
1264 refcount_acquire(&client->cl_refs);
1266 struct svc_rpc_gss_clientid *p;
1267 if (gc.gc_handle.length != sizeof(*p)) {
1268 result = AUTH_BADCRED;
1271 p = gc.gc_handle.value;
1272 client = svc_rpc_gss_find_client(p);
1275 * Can't find the client - we may have
1276 * destroyed it - tell the other side to
1279 result = RPCSEC_GSS_CREDPROBLEM;
1283 cc = rqst->rq_clntcred;
1284 cc->cc_client = client;
1285 cc->cc_service = gc.gc_svc;
1286 cc->cc_seq = gc.gc_seq;
1289 * The service and sequence number must be ignored for
1290 * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
1292 if (gc.gc_proc != RPCSEC_GSS_INIT
1293 && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
1295 * Check for sequence number overflow.
1297 if (gc.gc_seq >= MAXSEQ) {
1298 result = RPCSEC_GSS_CTXPROBLEM;
1303 * Check for valid service.
1305 if (gc.gc_svc != rpc_gss_svc_none &&
1306 gc.gc_svc != rpc_gss_svc_integrity &&
1307 gc.gc_svc != rpc_gss_svc_privacy) {
1308 result = AUTH_BADCRED;
1313 /* Handle RPCSEC_GSS control procedure. */
1314 switch (gc.gc_proc) {
1316 case RPCSEC_GSS_INIT:
1317 case RPCSEC_GSS_CONTINUE_INIT:
1318 if (rqst->rq_proc != NULLPROC) {
1319 result = AUTH_REJECTEDCRED;
1323 memset(&gr, 0, sizeof(gr));
1324 if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
1325 result = AUTH_REJECTEDCRED;
1329 if (gr.gr_major == GSS_S_COMPLETE) {
1331 * We borrow the space for the call verf to
1332 * pack our reply verf.
1334 rqst->rq_verf = msg->rm_call.cb_verf;
1335 if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
1336 result = AUTH_REJECTEDCRED;
1340 rqst->rq_verf = _null_auth;
1343 call_stat = svc_sendreply(rqst,
1344 (xdrproc_t) xdr_rpc_gss_init_res,
1347 gss_release_buffer(&min_stat, &gr.gr_token);
1350 result = AUTH_FAILED;
1354 if (gr.gr_major == GSS_S_COMPLETE)
1355 client->cl_state = CLIENT_ESTABLISHED;
1357 result = RPCSEC_GSS_NODISPATCH;
1360 case RPCSEC_GSS_DATA:
1361 case RPCSEC_GSS_DESTROY:
1362 if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
1363 result = RPCSEC_GSS_NODISPATCH;
1367 if (!svc_rpc_gss_validate(client, msg, &qop, gc.gc_proc)) {
1368 result = RPCSEC_GSS_CREDPROBLEM;
1373 * We borrow the space for the call verf to pack our
1376 rqst->rq_verf = msg->rm_call.cb_verf;
1377 if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
1378 result = RPCSEC_GSS_CTXPROBLEM;
1382 svc_rpc_gss_update_seq(client, gc.gc_seq);
1385 * Change the SVCAUTH ops on the request to point at
1386 * our own code so that we can unwrap the arguments
1387 * and wrap the result. The caller will re-set this on
1388 * every request to point to a set of null wrap/unwrap
1389 * methods. Acquire an extra reference to the client
1390 * which will be released by svc_rpc_gss_release()
1391 * after the request has finished processing.
1393 refcount_acquire(&client->cl_refs);
1394 rqst->rq_auth.svc_ah_ops = &svc_auth_gss_ops;
1395 rqst->rq_auth.svc_ah_private = cc;
1397 if (gc.gc_proc == RPCSEC_GSS_DATA) {
1399 * We might be ready to do a callback to the server to
1400 * see if it wants to accept/reject the connection.
1402 sx_xlock(&client->cl_lock);
1403 if (!client->cl_done_callback) {
1404 client->cl_done_callback = TRUE;
1405 client->cl_qop = qop;
1406 client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1407 client->cl_rawcred.mechanism, qop);
1408 if (!svc_rpc_gss_callback(client, rqst)) {
1409 result = AUTH_REJECTEDCRED;
1410 sx_xunlock(&client->cl_lock);
1414 sx_xunlock(&client->cl_lock);
1417 * If the server has locked this client to a
1418 * particular service+qop pair, enforce that
1421 if (client->cl_locked) {
1422 if (client->cl_rawcred.service != gc.gc_svc) {
1423 result = AUTH_FAILED;
1425 } else if (client->cl_qop != qop) {
1426 result = AUTH_BADVERF;
1432 * If the qop changed, look up the new qop
1435 if (client->cl_qop != qop) {
1436 client->cl_qop = qop;
1437 client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1438 client->cl_rawcred.mechanism, qop);
1442 * Make sure we use the right service value
1445 if (client->cl_rawcred.service != gc.gc_svc) {
1446 client->cl_rawcred.service = gc.gc_svc;
1447 svc_rpc_gss_set_flavor(client);
1452 if (rqst->rq_proc != NULLPROC) {
1453 result = AUTH_REJECTEDCRED;
1457 call_stat = svc_sendreply(rqst,
1458 (xdrproc_t) xdr_void, (caddr_t) NULL);
1461 result = AUTH_FAILED;
1465 svc_rpc_gss_forget_client(client);
1467 result = RPCSEC_GSS_NODISPATCH;
1473 result = AUTH_BADCRED;
1478 svc_rpc_gss_release_client(client);
1480 xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
1485 svc_rpc_gss_wrap(SVCAUTH *auth, struct mbuf **mp)
1487 struct svc_rpc_gss_cookedcred *cc;
1488 struct svc_rpc_gss_client *client;
1490 rpc_gss_log_debug("in svc_rpc_gss_wrap()");
1492 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1493 client = cc->cc_client;
1494 if (client->cl_state != CLIENT_ESTABLISHED
1495 || cc->cc_service == rpc_gss_svc_none || *mp == NULL) {
1499 return (xdr_rpc_gss_wrap_data(mp,
1500 client->cl_ctx, client->cl_qop,
1501 cc->cc_service, cc->cc_seq));
1505 svc_rpc_gss_unwrap(SVCAUTH *auth, struct mbuf **mp)
1507 struct svc_rpc_gss_cookedcred *cc;
1508 struct svc_rpc_gss_client *client;
1510 rpc_gss_log_debug("in svc_rpc_gss_unwrap()");
1512 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1513 client = cc->cc_client;
1514 if (client->cl_state != CLIENT_ESTABLISHED
1515 || cc->cc_service == rpc_gss_svc_none) {
1519 return (xdr_rpc_gss_unwrap_data(mp,
1520 client->cl_ctx, client->cl_qop,
1521 cc->cc_service, cc->cc_seq));
1525 svc_rpc_gss_release(SVCAUTH *auth)
1527 struct svc_rpc_gss_cookedcred *cc;
1528 struct svc_rpc_gss_client *client;
1530 rpc_gss_log_debug("in svc_rpc_gss_release()");
1532 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1533 client = cc->cc_client;
1534 svc_rpc_gss_release_client(client);