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 and free it if we can.
612 svc_rpc_gss_forget_client(struct svc_rpc_gss_client *client)
614 struct svc_rpc_gss_client_list *list;
616 list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
617 sx_xlock(&svc_rpc_gss_lock);
618 TAILQ_REMOVE(list, client, cl_link);
619 TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
620 svc_rpc_gss_client_count--;
621 sx_xunlock(&svc_rpc_gss_lock);
622 svc_rpc_gss_release_client(client);
626 svc_rpc_gss_timeout_clients(void)
628 struct svc_rpc_gss_client *client;
629 struct svc_rpc_gss_client *nclient;
630 time_t now = time_uptime;
632 rpc_gss_log_debug("in svc_rpc_gss_timeout_clients()");
635 * First enforce the max client limit. We keep
636 * svc_rpc_gss_clients in LRU order.
638 while (svc_rpc_gss_client_count > CLIENT_MAX)
639 svc_rpc_gss_forget_client(TAILQ_LAST(&svc_rpc_gss_clients,
640 svc_rpc_gss_client_list));
641 TAILQ_FOREACH_SAFE(client, &svc_rpc_gss_clients, cl_alllink, nclient) {
642 if (client->cl_state == CLIENT_STALE
643 || now > client->cl_expiration) {
644 rpc_gss_log_debug("expiring client %p", client);
645 svc_rpc_gss_forget_client(client);
652 * OID<->string routines. These are uuuuugly.
655 gss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
658 unsigned long number;
660 size_t string_length;
665 /* Decoded according to krb5/gssapi_krb5.c */
667 /* First determine the size of the string */
671 cp = (unsigned char *) oid->elements;
672 number = (unsigned long) cp[0];
673 sprintf(numstr, "%ld ", number/40);
674 string_length += strlen(numstr);
675 sprintf(numstr, "%ld ", number%40);
676 string_length += strlen(numstr);
677 for (i=1; i<oid->length; i++) {
678 if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
679 number = (number << 7) | (cp[i] & 0x7f);
684 return(GSS_S_FAILURE);
686 if ((cp[i] & 0x80) == 0) {
687 sprintf(numstr, "%ld ", number);
688 string_length += strlen(numstr);
694 * If we get here, we've calculated the length of "n n n ... n ". Add 4
695 * here for "{ " and "}\0".
698 if ((bp = (char *) mem_alloc(string_length))) {
700 number = (unsigned long) cp[0];
701 sprintf(numstr, "%ld ", number/40);
703 sprintf(numstr, "%ld ", number%40);
706 cp = (unsigned char *) oid->elements;
707 for (i=1; i<oid->length; i++) {
708 number = (number << 7) | (cp[i] & 0x7f);
709 if ((cp[i] & 0x80) == 0) {
710 sprintf(numstr, "%ld ", number);
716 oid_str->length = strlen(bp)+1;
717 oid_str->value = (void *) bp;
719 return(GSS_S_COMPLETE);
722 return(GSS_S_FAILURE);
727 svc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
728 const gss_name_t name)
730 OM_uint32 maj_stat, min_stat;
731 rpc_gss_ucred_t *uc = &client->cl_ucred;
736 uc->gidlist = client->cl_gid_storage;
739 maj_stat = gss_pname_to_unix_cred(&min_stat, name, client->cl_mech,
740 &uc->uid, &uc->gid, &numgroups, &uc->gidlist[0]);
741 if (GSS_ERROR(maj_stat))
744 uc->gidlen = numgroups;
748 svc_rpc_gss_set_flavor(struct svc_rpc_gss_client *client)
750 static gss_OID_desc krb5_mech_oid =
751 {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
754 * Attempt to translate mech type and service into a
755 * 'pseudo flavor'. Hardwire in krb5 support for now.
757 if (kgss_oid_equal(client->cl_mech, &krb5_mech_oid)) {
758 switch (client->cl_rawcred.service) {
759 case rpc_gss_svc_default:
760 case rpc_gss_svc_none:
761 client->cl_rpcflavor = RPCSEC_GSS_KRB5;
763 case rpc_gss_svc_integrity:
764 client->cl_rpcflavor = RPCSEC_GSS_KRB5I;
766 case rpc_gss_svc_privacy:
767 client->cl_rpcflavor = RPCSEC_GSS_KRB5P;
771 client->cl_rpcflavor = RPCSEC_GSS;
776 svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
777 struct svc_req *rqst,
778 struct rpc_gss_init_res *gr,
779 struct rpc_gss_cred *gc)
781 gss_buffer_desc recv_tok;
783 OM_uint32 maj_stat = 0, min_stat = 0, ret_flags;
784 OM_uint32 cred_lifetime;
785 struct svc_rpc_gss_svc_name *sname;
787 rpc_gss_log_debug("in svc_rpc_gss_accept_context()");
789 /* Deserialize arguments. */
790 memset(&recv_tok, 0, sizeof(recv_tok));
792 if (!svc_getargs(rqst,
793 (xdrproc_t) xdr_gss_buffer_desc,
794 (caddr_t) &recv_tok)) {
795 client->cl_state = CLIENT_STALE;
800 * First time round, try all the server names we have until
801 * one matches. Afterwards, stick with that one.
803 sx_xlock(&svc_rpc_gss_lock);
804 if (!client->cl_sname) {
805 SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
806 if (sname->sn_program == rqst->rq_prog
807 && sname->sn_version == rqst->rq_vers) {
809 gr->gr_major = gss_accept_sec_context(
814 GSS_C_NO_CHANNEL_BINDINGS,
822 GSS_S_CREDENTIALS_EXPIRED) {
824 * Either our creds really did
828 if (rpc_gss_acquire_svc_cred(sname))
831 client->cl_sname = sname;
836 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
838 sx_xunlock(&svc_rpc_gss_lock);
842 gr->gr_major = gss_accept_sec_context(
845 client->cl_sname->sn_cred,
847 GSS_C_NO_CHANNEL_BINDINGS,
855 sx_xunlock(&svc_rpc_gss_lock);
857 xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
860 * If we get an error from gss_accept_sec_context, send the
861 * reply anyway so that the client gets a chance to see what
864 if (gr->gr_major != GSS_S_COMPLETE &&
865 gr->gr_major != GSS_S_CONTINUE_NEEDED) {
866 rpc_gss_log_status("accept_sec_context", client->cl_mech,
867 gr->gr_major, gr->gr_minor);
868 client->cl_state = CLIENT_STALE;
872 gr->gr_handle.value = &client->cl_id;
873 gr->gr_handle.length = sizeof(client->cl_id);
874 gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
876 /* Save client info. */
877 client->cl_mech = mech;
878 client->cl_qop = GSS_C_QOP_DEFAULT;
879 client->cl_done_callback = FALSE;
881 if (gr->gr_major == GSS_S_COMPLETE) {
882 gss_buffer_desc export_name;
885 * Change client expiration time to be near when the
886 * client creds expire (or 24 hours if we can't figure
889 if (cred_lifetime == GSS_C_INDEFINITE)
890 cred_lifetime = time_uptime + 24*60*60;
892 client->cl_expiration = time_uptime + cred_lifetime;
895 * Fill in cred details in the rawcred structure.
897 client->cl_rawcred.version = RPCSEC_GSS_VERSION;
898 rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
899 maj_stat = gss_export_name(&min_stat, client->cl_cname,
901 if (maj_stat != GSS_S_COMPLETE) {
902 rpc_gss_log_status("gss_export_name", client->cl_mech,
906 client->cl_rawcred.client_principal =
907 mem_alloc(sizeof(*client->cl_rawcred.client_principal)
908 + export_name.length);
909 client->cl_rawcred.client_principal->len = export_name.length;
910 memcpy(client->cl_rawcred.client_principal->name,
911 export_name.value, export_name.length);
912 gss_release_buffer(&min_stat, &export_name);
913 client->cl_rawcred.svc_principal =
914 client->cl_sname->sn_principal;
915 client->cl_rawcred.service = gc->gc_svc;
918 * Use gss_pname_to_uid to map to unix creds. For
919 * kerberos5, this uses krb5_aname_to_localname.
921 svc_rpc_gss_build_ucred(client, client->cl_cname);
922 svc_rpc_gss_set_flavor(client);
923 gss_release_name(&min_stat, &client->cl_cname);
927 gss_buffer_desc mechname;
929 gss_oid_to_str(&min_stat, mech, &mechname);
931 rpc_gss_log_debug("accepted context for %s with "
932 "<mech %.*s, qop %d, svc %d>",
933 client->cl_rawcred.client_principal->name,
934 mechname.length, (char *)mechname.value,
935 client->cl_qop, client->rawcred.service);
937 gss_release_buffer(&min_stat, &mechname);
945 svc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
948 struct opaque_auth *oa;
949 gss_buffer_desc rpcbuf, checksum;
950 OM_uint32 maj_stat, min_stat;
952 int32_t rpchdr[128 / sizeof(int32_t)];
955 rpc_gss_log_debug("in svc_rpc_gss_validate()");
957 memset(rpchdr, 0, sizeof(rpchdr));
959 /* Reconstruct RPC header for signing (from xdr_callmsg). */
961 IXDR_PUT_LONG(buf, msg->rm_xid);
962 IXDR_PUT_ENUM(buf, msg->rm_direction);
963 IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
964 IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
965 IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
966 IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
967 oa = &msg->rm_call.cb_cred;
968 IXDR_PUT_ENUM(buf, oa->oa_flavor);
969 IXDR_PUT_LONG(buf, oa->oa_length);
971 memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
972 buf += RNDUP(oa->oa_length) / sizeof(int32_t);
974 rpcbuf.value = rpchdr;
975 rpcbuf.length = (u_char *)buf - (u_char *)rpchdr;
977 checksum.value = msg->rm_call.cb_verf.oa_base;
978 checksum.length = msg->rm_call.cb_verf.oa_length;
980 maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
983 if (maj_stat != GSS_S_COMPLETE) {
984 rpc_gss_log_status("gss_verify_mic", client->cl_mech,
986 client->cl_state = CLIENT_STALE;
995 svc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
996 struct svc_req *rqst, u_int seq)
998 gss_buffer_desc signbuf;
1000 OM_uint32 maj_stat, min_stat;
1003 rpc_gss_log_debug("in svc_rpc_gss_nextverf()");
1006 signbuf.value = &nseq;
1007 signbuf.length = sizeof(nseq);
1009 maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
1012 if (maj_stat != GSS_S_COMPLETE) {
1013 rpc_gss_log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
1014 client->cl_state = CLIENT_STALE;
1018 KASSERT(mic.length <= MAX_AUTH_BYTES,
1019 ("MIC too large for RPCSEC_GSS"));
1021 rqst->rq_verf.oa_flavor = RPCSEC_GSS;
1022 rqst->rq_verf.oa_length = mic.length;
1023 bcopy(mic.value, rqst->rq_verf.oa_base, mic.length);
1025 gss_release_buffer(&min_stat, &mic);
1031 svc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
1033 struct svc_rpc_gss_callback *scb;
1034 rpc_gss_lock_t lock;
1040 * See if we have a callback for this guy.
1043 SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
1044 if (scb->cb_callback.program == rqst->rq_prog
1045 && scb->cb_callback.version == rqst->rq_vers) {
1047 * This one matches. Call the callback and see
1048 * if it wants to veto or something.
1050 lock.locked = FALSE;
1051 lock.raw_cred = &client->cl_rawcred;
1052 cb_res = scb->cb_callback.callback(rqst,
1059 client->cl_state = CLIENT_STALE;
1065 * The callback accepted the connection - it
1066 * is responsible for freeing client->cl_creds
1069 client->cl_creds = GSS_C_NO_CREDENTIAL;
1070 client->cl_locked = lock.locked;
1071 client->cl_cookie = cookie;
1077 * Either no callback exists for this program/version or one
1078 * of the callbacks rejected the connection. We just need to
1079 * clean up the delegated client creds, if any.
1081 if (client->cl_creds) {
1083 gss_release_cred(&min_ver, &client->cl_creds);
1089 svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
1095 sx_xlock(&client->cl_lock);
1096 if (seq <= client->cl_seqlast) {
1098 * The request sequence number is less than
1099 * the largest we have seen so far. If it is
1100 * outside the window or if we have seen a
1101 * request with this sequence before, silently
1104 offset = client->cl_seqlast - seq;
1105 if (offset >= SVC_RPC_GSS_SEQWINDOW) {
1111 if (client->cl_seqmask[word] & (1 << bit)) {
1119 sx_xunlock(&client->cl_lock);
1124 svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
1126 int offset, i, word, bit;
1127 uint32_t carry, newcarry;
1129 sx_xlock(&client->cl_lock);
1130 if (seq > client->cl_seqlast) {
1132 * This request has a sequence number greater
1133 * than any we have seen so far. Advance the
1134 * seq window and set bit zero of the window
1135 * (which corresponds to the new sequence
1138 offset = seq - client->cl_seqlast;
1139 while (offset > 32) {
1140 for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
1142 client->cl_seqmask[i] = client->cl_seqmask[i-1];
1144 client->cl_seqmask[0] = 0;
1148 for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
1149 newcarry = client->cl_seqmask[i] >> (32 - offset);
1150 client->cl_seqmask[i] =
1151 (client->cl_seqmask[i] << offset) | carry;
1154 client->cl_seqmask[0] |= 1;
1155 client->cl_seqlast = seq;
1157 offset = client->cl_seqlast - seq;
1160 client->cl_seqmask[word] |= (1 << bit);
1162 sx_xunlock(&client->cl_lock);
1166 svc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
1171 struct svc_rpc_gss_cookedcred *cc;
1172 struct svc_rpc_gss_client *client;
1173 struct rpc_gss_cred gc;
1174 struct rpc_gss_init_res gr;
1177 enum auth_stat result;
1179 rpc_gss_log_debug("in svc_rpc_gss()");
1181 /* Garbage collect old clients. */
1182 svc_rpc_gss_timeout_clients();
1184 /* Initialize reply. */
1185 rqst->rq_verf = _null_auth;
1187 /* Deserialize client credentials. */
1188 if (rqst->rq_cred.oa_length <= 0)
1189 return (AUTH_BADCRED);
1191 memset(&gc, 0, sizeof(gc));
1193 xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
1194 rqst->rq_cred.oa_length, XDR_DECODE);
1196 if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
1198 return (AUTH_BADCRED);
1204 /* Check version. */
1205 if (gc.gc_version != RPCSEC_GSS_VERSION) {
1206 result = AUTH_BADCRED;
1210 /* Check the proc and find the client (or create it) */
1211 if (gc.gc_proc == RPCSEC_GSS_INIT) {
1212 if (gc.gc_handle.length != 0) {
1213 result = AUTH_BADCRED;
1216 client = svc_rpc_gss_create_client();
1217 refcount_acquire(&client->cl_refs);
1219 struct svc_rpc_gss_clientid *p;
1220 if (gc.gc_handle.length != sizeof(*p)) {
1221 result = AUTH_BADCRED;
1224 p = gc.gc_handle.value;
1225 client = svc_rpc_gss_find_client(p);
1228 * Can't find the client - we may have
1229 * destroyed it - tell the other side to
1232 result = RPCSEC_GSS_CREDPROBLEM;
1236 cc = rqst->rq_clntcred;
1237 cc->cc_client = client;
1238 cc->cc_service = gc.gc_svc;
1239 cc->cc_seq = gc.gc_seq;
1242 * The service and sequence number must be ignored for
1243 * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
1245 if (gc.gc_proc != RPCSEC_GSS_INIT
1246 && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
1248 * Check for sequence number overflow.
1250 if (gc.gc_seq >= MAXSEQ) {
1251 result = RPCSEC_GSS_CTXPROBLEM;
1256 * Check for valid service.
1258 if (gc.gc_svc != rpc_gss_svc_none &&
1259 gc.gc_svc != rpc_gss_svc_integrity &&
1260 gc.gc_svc != rpc_gss_svc_privacy) {
1261 result = AUTH_BADCRED;
1266 /* Handle RPCSEC_GSS control procedure. */
1267 switch (gc.gc_proc) {
1269 case RPCSEC_GSS_INIT:
1270 case RPCSEC_GSS_CONTINUE_INIT:
1271 if (rqst->rq_proc != NULLPROC) {
1272 result = AUTH_REJECTEDCRED;
1276 memset(&gr, 0, sizeof(gr));
1277 if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
1278 result = AUTH_REJECTEDCRED;
1282 if (gr.gr_major == GSS_S_COMPLETE) {
1284 * We borrow the space for the call verf to
1285 * pack our reply verf.
1287 rqst->rq_verf = msg->rm_call.cb_verf;
1288 if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
1289 result = AUTH_REJECTEDCRED;
1293 rqst->rq_verf = _null_auth;
1296 call_stat = svc_sendreply(rqst,
1297 (xdrproc_t) xdr_rpc_gss_init_res,
1300 gss_release_buffer(&min_stat, &gr.gr_token);
1303 result = AUTH_FAILED;
1307 if (gr.gr_major == GSS_S_COMPLETE)
1308 client->cl_state = CLIENT_ESTABLISHED;
1310 result = RPCSEC_GSS_NODISPATCH;
1313 case RPCSEC_GSS_DATA:
1314 case RPCSEC_GSS_DESTROY:
1315 if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
1316 result = RPCSEC_GSS_NODISPATCH;
1320 if (!svc_rpc_gss_validate(client, msg, &qop)) {
1321 result = RPCSEC_GSS_CREDPROBLEM;
1326 * We borrow the space for the call verf to pack our
1329 rqst->rq_verf = msg->rm_call.cb_verf;
1330 if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
1331 result = RPCSEC_GSS_CTXPROBLEM;
1335 svc_rpc_gss_update_seq(client, gc.gc_seq);
1338 * Change the SVCAUTH ops on the request to point at
1339 * our own code so that we can unwrap the arguments
1340 * and wrap the result. The caller will re-set this on
1341 * every request to point to a set of null wrap/unwrap
1342 * methods. Acquire an extra reference to the client
1343 * which will be released by svc_rpc_gss_release()
1344 * after the request has finished processing.
1346 refcount_acquire(&client->cl_refs);
1347 rqst->rq_auth.svc_ah_ops = &svc_auth_gss_ops;
1348 rqst->rq_auth.svc_ah_private = cc;
1350 if (gc.gc_proc == RPCSEC_GSS_DATA) {
1352 * We might be ready to do a callback to the server to
1353 * see if it wants to accept/reject the connection.
1355 sx_xlock(&client->cl_lock);
1356 if (!client->cl_done_callback) {
1357 client->cl_done_callback = TRUE;
1358 client->cl_qop = qop;
1359 client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1360 client->cl_rawcred.mechanism, qop);
1361 if (!svc_rpc_gss_callback(client, rqst)) {
1362 result = AUTH_REJECTEDCRED;
1363 sx_xunlock(&client->cl_lock);
1367 sx_xunlock(&client->cl_lock);
1370 * If the server has locked this client to a
1371 * particular service+qop pair, enforce that
1374 if (client->cl_locked) {
1375 if (client->cl_rawcred.service != gc.gc_svc) {
1376 result = AUTH_FAILED;
1378 } else if (client->cl_qop != qop) {
1379 result = AUTH_BADVERF;
1385 * If the qop changed, look up the new qop
1388 if (client->cl_qop != qop) {
1389 client->cl_qop = qop;
1390 client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1391 client->cl_rawcred.mechanism, qop);
1395 * Make sure we use the right service value
1398 if (client->cl_rawcred.service != gc.gc_svc) {
1399 client->cl_rawcred.service = gc.gc_svc;
1400 svc_rpc_gss_set_flavor(client);
1405 if (rqst->rq_proc != NULLPROC) {
1406 result = AUTH_REJECTEDCRED;
1410 call_stat = svc_sendreply(rqst,
1411 (xdrproc_t) xdr_void, (caddr_t) NULL);
1414 result = AUTH_FAILED;
1418 svc_rpc_gss_forget_client(client);
1420 result = RPCSEC_GSS_NODISPATCH;
1426 result = AUTH_BADCRED;
1431 svc_rpc_gss_release_client(client);
1433 xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
1438 svc_rpc_gss_wrap(SVCAUTH *auth, struct mbuf **mp)
1440 struct svc_rpc_gss_cookedcred *cc;
1441 struct svc_rpc_gss_client *client;
1443 rpc_gss_log_debug("in svc_rpc_gss_wrap()");
1445 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1446 client = cc->cc_client;
1447 if (client->cl_state != CLIENT_ESTABLISHED
1448 || cc->cc_service == rpc_gss_svc_none || *mp == NULL) {
1452 return (xdr_rpc_gss_wrap_data(mp,
1453 client->cl_ctx, client->cl_qop,
1454 cc->cc_service, cc->cc_seq));
1458 svc_rpc_gss_unwrap(SVCAUTH *auth, struct mbuf **mp)
1460 struct svc_rpc_gss_cookedcred *cc;
1461 struct svc_rpc_gss_client *client;
1463 rpc_gss_log_debug("in svc_rpc_gss_unwrap()");
1465 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1466 client = cc->cc_client;
1467 if (client->cl_state != CLIENT_ESTABLISHED
1468 || cc->cc_service == rpc_gss_svc_none) {
1472 return (xdr_rpc_gss_unwrap_data(mp,
1473 client->cl_ctx, client->cl_qop,
1474 cc->cc_service, cc->cc_seq));
1478 svc_rpc_gss_release(SVCAUTH *auth)
1480 struct svc_rpc_gss_cookedcred *cc;
1481 struct svc_rpc_gss_client *client;
1483 rpc_gss_log_debug("in svc_rpc_gss_release()");
1485 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1486 client = cc->cc_client;
1487 svc_rpc_gss_release_client(client);