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
124 #ifndef RPCAUTH_UNIXGIDS
125 #define RPCAUTH_UNIXGIDS 16
128 struct svc_rpc_gss_clientid {
129 unsigned long ci_hostid;
130 uint32_t ci_boottime;
134 struct svc_rpc_gss_client {
135 TAILQ_ENTRY(svc_rpc_gss_client) cl_link;
136 TAILQ_ENTRY(svc_rpc_gss_client) cl_alllink;
137 volatile u_int cl_refs;
139 struct svc_rpc_gss_clientid cl_id;
140 time_t cl_expiration; /* when to gc */
141 enum svc_rpc_gss_client_state cl_state; /* client state */
142 bool_t cl_locked; /* fixed service+qop */
143 gss_ctx_id_t cl_ctx; /* context id */
144 gss_cred_id_t cl_creds; /* delegated creds */
145 gss_name_t cl_cname; /* client name */
146 struct svc_rpc_gss_svc_name *cl_sname; /* server name used */
147 rpc_gss_rawcred_t cl_rawcred; /* raw credentials */
148 rpc_gss_ucred_t cl_ucred; /* unix-style credentials */
149 struct ucred *cl_cred; /* kernel-style credentials */
150 int cl_rpcflavor; /* RPC pseudo sec flavor */
151 bool_t cl_done_callback; /* TRUE after call */
152 void *cl_cookie; /* user cookie from callback */
153 gid_t cl_gid_storage[RPCAUTH_UNIXGIDS];
154 gss_OID cl_mech; /* mechanism */
155 gss_qop_t cl_qop; /* quality of protection */
156 uint32_t cl_seqlast; /* sequence window origin */
157 uint32_t cl_seqmask[SVC_RPC_GSS_SEQWINDOW/32]; /* bitmask of seqnums */
159 TAILQ_HEAD(svc_rpc_gss_client_list, svc_rpc_gss_client);
162 * This structure holds enough information to unwrap arguments or wrap
163 * results for a given request. We use the rq_clntcred area for this
164 * (which is a per-request buffer).
166 struct svc_rpc_gss_cookedcred {
167 struct svc_rpc_gss_client *cc_client;
168 rpc_gss_service_t cc_service;
172 #define CLIENT_HASH_SIZE 256
173 #define CLIENT_MAX 128
174 struct svc_rpc_gss_client_list svc_rpc_gss_client_hash[CLIENT_HASH_SIZE];
175 struct svc_rpc_gss_client_list svc_rpc_gss_clients;
176 static size_t svc_rpc_gss_client_count;
177 static uint32_t svc_rpc_gss_next_clientid = 1;
180 svc_rpc_gss_init(void *arg)
184 for (i = 0; i < CLIENT_HASH_SIZE; i++)
185 TAILQ_INIT(&svc_rpc_gss_client_hash[i]);
186 TAILQ_INIT(&svc_rpc_gss_clients);
187 svc_auth_reg(RPCSEC_GSS, svc_rpc_gss, rpc_gss_svc_getcred);
188 sx_init(&svc_rpc_gss_lock, "gsslock");
190 SYSINIT(svc_rpc_gss_init, SI_SUB_KMEM, SI_ORDER_ANY, svc_rpc_gss_init, NULL);
193 rpc_gss_set_callback(rpc_gss_callback_t *cb)
195 struct svc_rpc_gss_callback *scb;
197 scb = mem_alloc(sizeof(struct svc_rpc_gss_callback));
199 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
202 scb->cb_callback = *cb;
203 sx_xlock(&svc_rpc_gss_lock);
204 SLIST_INSERT_HEAD(&svc_rpc_gss_callbacks, scb, cb_link);
205 sx_xunlock(&svc_rpc_gss_lock);
211 rpc_gss_clear_callback(rpc_gss_callback_t *cb)
213 struct svc_rpc_gss_callback *scb;
215 sx_xlock(&svc_rpc_gss_lock);
216 SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
217 if (scb->cb_callback.program == cb->program
218 && scb->cb_callback.version == cb->version
219 && scb->cb_callback.callback == cb->callback) {
220 SLIST_REMOVE(&svc_rpc_gss_callbacks, scb,
221 svc_rpc_gss_callback, cb_link);
222 sx_xunlock(&svc_rpc_gss_lock);
223 mem_free(scb, sizeof(*scb));
227 sx_xunlock(&svc_rpc_gss_lock);
231 rpc_gss_acquire_svc_cred(struct svc_rpc_gss_svc_name *sname)
233 OM_uint32 maj_stat, min_stat;
234 gss_buffer_desc namebuf;
236 gss_OID_set_desc oid_set;
239 oid_set.elements = sname->sn_mech;
241 namebuf.value = (void *) sname->sn_principal;
242 namebuf.length = strlen(sname->sn_principal);
244 maj_stat = gss_import_name(&min_stat, &namebuf,
245 GSS_C_NT_HOSTBASED_SERVICE, &name);
246 if (maj_stat != GSS_S_COMPLETE)
249 if (sname->sn_cred != GSS_C_NO_CREDENTIAL)
250 gss_release_cred(&min_stat, &sname->sn_cred);
252 maj_stat = gss_acquire_cred(&min_stat, name,
253 sname->sn_req_time, &oid_set, GSS_C_ACCEPT, &sname->sn_cred,
255 if (maj_stat != GSS_S_COMPLETE) {
256 gss_release_name(&min_stat, &name);
259 gss_release_name(&min_stat, &name);
265 rpc_gss_set_svc_name(const char *principal, const char *mechanism,
266 u_int req_time, u_int program, u_int version)
268 struct svc_rpc_gss_svc_name *sname;
271 if (!rpc_gss_mech_to_oid(mechanism, &mech_oid))
274 sname = mem_alloc(sizeof(*sname));
277 sname->sn_principal = strdup(principal, M_RPC);
278 sname->sn_mech = mech_oid;
279 sname->sn_req_time = req_time;
280 sname->sn_cred = GSS_C_NO_CREDENTIAL;
281 sname->sn_program = program;
282 sname->sn_version = version;
284 if (!rpc_gss_acquire_svc_cred(sname)) {
285 free(sname->sn_principal, M_RPC);
286 mem_free(sname, sizeof(*sname));
290 sx_xlock(&svc_rpc_gss_lock);
291 SLIST_INSERT_HEAD(&svc_rpc_gss_svc_names, sname, sn_link);
292 sx_xunlock(&svc_rpc_gss_lock);
298 rpc_gss_clear_svc_name(u_int program, u_int version)
301 struct svc_rpc_gss_svc_name *sname;
303 sx_xlock(&svc_rpc_gss_lock);
304 SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
305 if (sname->sn_program == program
306 && sname->sn_version == version) {
307 SLIST_REMOVE(&svc_rpc_gss_svc_names, sname,
308 svc_rpc_gss_svc_name, sn_link);
309 sx_xunlock(&svc_rpc_gss_lock);
310 gss_release_cred(&min_stat, &sname->sn_cred);
311 free(sname->sn_principal, M_RPC);
312 mem_free(sname, sizeof(*sname));
316 sx_xunlock(&svc_rpc_gss_lock);
320 rpc_gss_get_principal_name(rpc_gss_principal_t *principal,
321 const char *mech, const char *name, const char *node, const char *domain)
323 OM_uint32 maj_stat, min_stat;
327 gss_name_t gss_name, gss_mech_name;
328 rpc_gss_principal_t result;
330 if (!rpc_gss_mech_to_oid(mech, &mech_oid))
334 * Construct a gss_buffer containing the full name formatted
335 * as "name/node@domain" where node and domain are optional.
337 namelen = strlen(name);
339 namelen += strlen(node) + 1;
342 namelen += strlen(domain) + 1;
345 buf.value = mem_alloc(namelen);
346 buf.length = namelen;
347 strcpy((char *) buf.value, name);
349 strcat((char *) buf.value, "/");
350 strcat((char *) buf.value, node);
353 strcat((char *) buf.value, "@");
354 strcat((char *) buf.value, domain);
358 * Convert that to a gss_name_t and then convert that to a
359 * mechanism name in the selected mechanism.
361 maj_stat = gss_import_name(&min_stat, &buf,
362 GSS_C_NT_USER_NAME, &gss_name);
363 mem_free(buf.value, buf.length);
364 if (maj_stat != GSS_S_COMPLETE) {
365 rpc_gss_log_status("gss_import_name", mech_oid, maj_stat, min_stat);
368 maj_stat = gss_canonicalize_name(&min_stat, gss_name, mech_oid,
370 if (maj_stat != GSS_S_COMPLETE) {
371 rpc_gss_log_status("gss_canonicalize_name", mech_oid, maj_stat,
373 gss_release_name(&min_stat, &gss_name);
376 gss_release_name(&min_stat, &gss_name);
379 * Export the mechanism name and use that to construct the
380 * rpc_gss_principal_t result.
382 maj_stat = gss_export_name(&min_stat, gss_mech_name, &buf);
383 if (maj_stat != GSS_S_COMPLETE) {
384 rpc_gss_log_status("gss_export_name", mech_oid, maj_stat, min_stat);
385 gss_release_name(&min_stat, &gss_mech_name);
388 gss_release_name(&min_stat, &gss_mech_name);
390 result = mem_alloc(sizeof(int) + buf.length);
392 gss_release_buffer(&min_stat, &buf);
395 result->len = buf.length;
396 memcpy(result->name, buf.value, buf.length);
397 gss_release_buffer(&min_stat, &buf);
404 rpc_gss_getcred(struct svc_req *req, rpc_gss_rawcred_t **rcred,
405 rpc_gss_ucred_t **ucred, void **cookie)
407 struct svc_rpc_gss_cookedcred *cc;
408 struct svc_rpc_gss_client *client;
410 if (req->rq_cred.oa_flavor != RPCSEC_GSS)
413 cc = req->rq_clntcred;
414 client = cc->cc_client;
416 *rcred = &client->cl_rawcred;
418 *ucred = &client->cl_ucred;
420 *cookie = client->cl_cookie;
425 * This simpler interface is used by svc_getcred to copy the cred data
426 * into a kernel cred structure.
429 rpc_gss_svc_getcred(struct svc_req *req, struct ucred **crp, int *flavorp)
432 struct svc_rpc_gss_cookedcred *cc;
433 struct svc_rpc_gss_client *client;
436 if (req->rq_cred.oa_flavor != RPCSEC_GSS)
439 cc = req->rq_clntcred;
440 client = cc->cc_client;
443 *flavorp = client->cl_rpcflavor;
445 if (client->cl_cred) {
446 *crp = crhold(client->cl_cred);
450 uc = &client->cl_ucred;
451 cr = client->cl_cred = crget();
452 cr->cr_uid = cr->cr_ruid = cr->cr_svuid = uc->uid;
453 cr->cr_rgid = cr->cr_svgid = uc->gid;
454 crsetgroups(cr, uc->gidlen, uc->gidlist);
455 cr->cr_prison = &prison0;
456 prison_hold(cr->cr_prison);
463 rpc_gss_svc_max_data_length(struct svc_req *req, int max_tp_unit_len)
465 struct svc_rpc_gss_cookedcred *cc = req->rq_clntcred;
466 struct svc_rpc_gss_client *client = cc->cc_client;
469 OM_uint32 maj_stat, min_stat;
472 switch (client->cl_rawcred.service) {
473 case rpc_gss_svc_none:
474 return (max_tp_unit_len);
477 case rpc_gss_svc_default:
478 case rpc_gss_svc_integrity:
482 case rpc_gss_svc_privacy:
490 maj_stat = gss_wrap_size_limit(&min_stat, client->cl_ctx, want_conf,
491 client->cl_qop, max_tp_unit_len, &max);
493 if (maj_stat == GSS_S_COMPLETE) {
499 rpc_gss_log_status("gss_wrap_size_limit", client->cl_mech,
505 static struct svc_rpc_gss_client *
506 svc_rpc_gss_find_client(struct svc_rpc_gss_clientid *id)
508 struct svc_rpc_gss_client *client;
509 struct svc_rpc_gss_client_list *list;
510 unsigned long hostid;
512 rpc_gss_log_debug("in svc_rpc_gss_find_client(%d)", id->ci_id);
514 getcredhostid(curthread->td_ucred, &hostid);
515 if (id->ci_hostid != hostid || id->ci_boottime != boottime.tv_sec)
518 list = &svc_rpc_gss_client_hash[id->ci_id % CLIENT_HASH_SIZE];
519 sx_xlock(&svc_rpc_gss_lock);
520 TAILQ_FOREACH(client, list, cl_link) {
521 if (client->cl_id.ci_id == id->ci_id) {
523 * Move this client to the front of the LRU
526 TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
527 TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client,
529 refcount_acquire(&client->cl_refs);
533 sx_xunlock(&svc_rpc_gss_lock);
538 static struct svc_rpc_gss_client *
539 svc_rpc_gss_create_client(void)
541 struct svc_rpc_gss_client *client;
542 struct svc_rpc_gss_client_list *list;
543 unsigned long hostid;
545 rpc_gss_log_debug("in svc_rpc_gss_create_client()");
547 client = mem_alloc(sizeof(struct svc_rpc_gss_client));
548 memset(client, 0, sizeof(struct svc_rpc_gss_client));
549 refcount_init(&client->cl_refs, 1);
550 sx_init(&client->cl_lock, "GSS-client");
551 getcredhostid(curthread->td_ucred, &hostid);
552 client->cl_id.ci_hostid = hostid;
553 client->cl_id.ci_boottime = boottime.tv_sec;
554 client->cl_id.ci_id = svc_rpc_gss_next_clientid++;
555 list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
556 sx_xlock(&svc_rpc_gss_lock);
557 TAILQ_INSERT_HEAD(list, client, cl_link);
558 TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client, cl_alllink);
559 svc_rpc_gss_client_count++;
560 sx_xunlock(&svc_rpc_gss_lock);
563 * Start the client off with a short expiration time. We will
564 * try to get a saner value from the client creds later.
566 client->cl_state = CLIENT_NEW;
567 client->cl_locked = FALSE;
568 client->cl_expiration = time_uptime + 5*60;
574 svc_rpc_gss_destroy_client(struct svc_rpc_gss_client *client)
578 rpc_gss_log_debug("in svc_rpc_gss_destroy_client()");
581 gss_delete_sec_context(&min_stat,
582 &client->cl_ctx, GSS_C_NO_BUFFER);
584 if (client->cl_cname)
585 gss_release_name(&min_stat, &client->cl_cname);
587 if (client->cl_rawcred.client_principal)
588 mem_free(client->cl_rawcred.client_principal,
589 sizeof(*client->cl_rawcred.client_principal)
590 + client->cl_rawcred.client_principal->len);
593 crfree(client->cl_cred);
595 sx_destroy(&client->cl_lock);
596 mem_free(client, sizeof(*client));
600 * Drop a reference to a client and free it if that was the last reference.
603 svc_rpc_gss_release_client(struct svc_rpc_gss_client *client)
606 if (!refcount_release(&client->cl_refs))
608 svc_rpc_gss_destroy_client(client);
612 * Remove a client from our global lists and free it if we can.
615 svc_rpc_gss_forget_client(struct svc_rpc_gss_client *client)
617 struct svc_rpc_gss_client_list *list;
619 list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
620 sx_xlock(&svc_rpc_gss_lock);
621 TAILQ_REMOVE(list, client, cl_link);
622 TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
623 svc_rpc_gss_client_count--;
624 sx_xunlock(&svc_rpc_gss_lock);
625 svc_rpc_gss_release_client(client);
629 svc_rpc_gss_timeout_clients(void)
631 struct svc_rpc_gss_client *client;
632 struct svc_rpc_gss_client *nclient;
633 time_t now = time_uptime;
635 rpc_gss_log_debug("in svc_rpc_gss_timeout_clients()");
638 * First enforce the max client limit. We keep
639 * svc_rpc_gss_clients in LRU order.
641 while (svc_rpc_gss_client_count > CLIENT_MAX)
642 svc_rpc_gss_forget_client(TAILQ_LAST(&svc_rpc_gss_clients,
643 svc_rpc_gss_client_list));
644 TAILQ_FOREACH_SAFE(client, &svc_rpc_gss_clients, cl_alllink, nclient) {
645 if (client->cl_state == CLIENT_STALE
646 || now > client->cl_expiration) {
647 rpc_gss_log_debug("expiring client %p", client);
648 svc_rpc_gss_forget_client(client);
655 * OID<->string routines. These are uuuuugly.
658 gss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
661 unsigned long number;
663 size_t string_length;
668 /* Decoded according to krb5/gssapi_krb5.c */
670 /* First determine the size of the string */
674 cp = (unsigned char *) oid->elements;
675 number = (unsigned long) cp[0];
676 sprintf(numstr, "%ld ", number/40);
677 string_length += strlen(numstr);
678 sprintf(numstr, "%ld ", number%40);
679 string_length += strlen(numstr);
680 for (i=1; i<oid->length; i++) {
681 if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
682 number = (number << 7) | (cp[i] & 0x7f);
687 return(GSS_S_FAILURE);
689 if ((cp[i] & 0x80) == 0) {
690 sprintf(numstr, "%ld ", number);
691 string_length += strlen(numstr);
697 * If we get here, we've calculated the length of "n n n ... n ". Add 4
698 * here for "{ " and "}\0".
701 if ((bp = (char *) mem_alloc(string_length))) {
703 number = (unsigned long) cp[0];
704 sprintf(numstr, "%ld ", number/40);
706 sprintf(numstr, "%ld ", number%40);
709 cp = (unsigned char *) oid->elements;
710 for (i=1; i<oid->length; i++) {
711 number = (number << 7) | (cp[i] & 0x7f);
712 if ((cp[i] & 0x80) == 0) {
713 sprintf(numstr, "%ld ", number);
719 oid_str->length = strlen(bp)+1;
720 oid_str->value = (void *) bp;
722 return(GSS_S_COMPLETE);
725 return(GSS_S_FAILURE);
730 svc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
731 const gss_name_t name)
733 OM_uint32 maj_stat, min_stat;
734 rpc_gss_ucred_t *uc = &client->cl_ucred;
739 uc->gidlist = client->cl_gid_storage;
741 numgroups = RPCAUTH_UNIXGIDS;
742 maj_stat = gss_pname_to_unix_cred(&min_stat, name, client->cl_mech,
743 &uc->uid, &uc->gid, &numgroups, &uc->gidlist[0]);
744 if (GSS_ERROR(maj_stat))
747 uc->gidlen = numgroups;
751 svc_rpc_gss_set_flavor(struct svc_rpc_gss_client *client)
753 static gss_OID_desc krb5_mech_oid =
754 {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
757 * Attempt to translate mech type and service into a
758 * 'pseudo flavor'. Hardwire in krb5 support for now.
760 if (kgss_oid_equal(client->cl_mech, &krb5_mech_oid)) {
761 switch (client->cl_rawcred.service) {
762 case rpc_gss_svc_default:
763 case rpc_gss_svc_none:
764 client->cl_rpcflavor = RPCSEC_GSS_KRB5;
766 case rpc_gss_svc_integrity:
767 client->cl_rpcflavor = RPCSEC_GSS_KRB5I;
769 case rpc_gss_svc_privacy:
770 client->cl_rpcflavor = RPCSEC_GSS_KRB5P;
774 client->cl_rpcflavor = RPCSEC_GSS;
779 svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
780 struct svc_req *rqst,
781 struct rpc_gss_init_res *gr,
782 struct rpc_gss_cred *gc)
784 gss_buffer_desc recv_tok;
786 OM_uint32 maj_stat = 0, min_stat = 0, ret_flags;
787 OM_uint32 cred_lifetime;
788 struct svc_rpc_gss_svc_name *sname;
790 rpc_gss_log_debug("in svc_rpc_gss_accept_context()");
792 /* Deserialize arguments. */
793 memset(&recv_tok, 0, sizeof(recv_tok));
795 if (!svc_getargs(rqst,
796 (xdrproc_t) xdr_gss_buffer_desc,
797 (caddr_t) &recv_tok)) {
798 client->cl_state = CLIENT_STALE;
803 * First time round, try all the server names we have until
804 * one matches. Afterwards, stick with that one.
806 sx_xlock(&svc_rpc_gss_lock);
807 if (!client->cl_sname) {
808 SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
809 if (sname->sn_program == rqst->rq_prog
810 && sname->sn_version == rqst->rq_vers) {
812 gr->gr_major = gss_accept_sec_context(
817 GSS_C_NO_CHANNEL_BINDINGS,
825 GSS_S_CREDENTIALS_EXPIRED) {
827 * Either our creds really did
831 if (rpc_gss_acquire_svc_cred(sname))
834 client->cl_sname = sname;
839 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
841 sx_xunlock(&svc_rpc_gss_lock);
845 gr->gr_major = gss_accept_sec_context(
848 client->cl_sname->sn_cred,
850 GSS_C_NO_CHANNEL_BINDINGS,
858 sx_xunlock(&svc_rpc_gss_lock);
860 xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
863 * If we get an error from gss_accept_sec_context, send the
864 * reply anyway so that the client gets a chance to see what
867 if (gr->gr_major != GSS_S_COMPLETE &&
868 gr->gr_major != GSS_S_CONTINUE_NEEDED) {
869 rpc_gss_log_status("accept_sec_context", client->cl_mech,
870 gr->gr_major, gr->gr_minor);
871 client->cl_state = CLIENT_STALE;
875 gr->gr_handle.value = &client->cl_id;
876 gr->gr_handle.length = sizeof(client->cl_id);
877 gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
879 /* Save client info. */
880 client->cl_mech = mech;
881 client->cl_qop = GSS_C_QOP_DEFAULT;
882 client->cl_done_callback = FALSE;
884 if (gr->gr_major == GSS_S_COMPLETE) {
885 gss_buffer_desc export_name;
888 * Change client expiration time to be near when the
889 * client creds expire (or 24 hours if we can't figure
892 if (cred_lifetime == GSS_C_INDEFINITE)
893 cred_lifetime = time_uptime + 24*60*60;
895 client->cl_expiration = time_uptime + cred_lifetime;
898 * Fill in cred details in the rawcred structure.
900 client->cl_rawcred.version = RPCSEC_GSS_VERSION;
901 rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
902 maj_stat = gss_export_name(&min_stat, client->cl_cname,
904 if (maj_stat != GSS_S_COMPLETE) {
905 rpc_gss_log_status("gss_export_name", client->cl_mech,
909 client->cl_rawcred.client_principal =
910 mem_alloc(sizeof(*client->cl_rawcred.client_principal)
911 + export_name.length);
912 client->cl_rawcred.client_principal->len = export_name.length;
913 memcpy(client->cl_rawcred.client_principal->name,
914 export_name.value, export_name.length);
915 gss_release_buffer(&min_stat, &export_name);
916 client->cl_rawcred.svc_principal =
917 client->cl_sname->sn_principal;
918 client->cl_rawcred.service = gc->gc_svc;
921 * Use gss_pname_to_uid to map to unix creds. For
922 * kerberos5, this uses krb5_aname_to_localname.
924 svc_rpc_gss_build_ucred(client, client->cl_cname);
925 svc_rpc_gss_set_flavor(client);
926 gss_release_name(&min_stat, &client->cl_cname);
930 gss_buffer_desc mechname;
932 gss_oid_to_str(&min_stat, mech, &mechname);
934 rpc_gss_log_debug("accepted context for %s with "
935 "<mech %.*s, qop %d, svc %d>",
936 client->cl_rawcred.client_principal->name,
937 mechname.length, (char *)mechname.value,
938 client->cl_qop, client->cl_rawcred.service);
940 gss_release_buffer(&min_stat, &mechname);
948 svc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
951 struct opaque_auth *oa;
952 gss_buffer_desc rpcbuf, checksum;
953 OM_uint32 maj_stat, min_stat;
955 int32_t rpchdr[128 / sizeof(int32_t)];
958 rpc_gss_log_debug("in svc_rpc_gss_validate()");
960 memset(rpchdr, 0, sizeof(rpchdr));
962 /* Reconstruct RPC header for signing (from xdr_callmsg). */
964 IXDR_PUT_LONG(buf, msg->rm_xid);
965 IXDR_PUT_ENUM(buf, msg->rm_direction);
966 IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
967 IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
968 IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
969 IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
970 oa = &msg->rm_call.cb_cred;
971 IXDR_PUT_ENUM(buf, oa->oa_flavor);
972 IXDR_PUT_LONG(buf, oa->oa_length);
974 memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
975 buf += RNDUP(oa->oa_length) / sizeof(int32_t);
977 rpcbuf.value = rpchdr;
978 rpcbuf.length = (u_char *)buf - (u_char *)rpchdr;
980 checksum.value = msg->rm_call.cb_verf.oa_base;
981 checksum.length = msg->rm_call.cb_verf.oa_length;
983 maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
986 if (maj_stat != GSS_S_COMPLETE) {
987 rpc_gss_log_status("gss_verify_mic", client->cl_mech,
989 client->cl_state = CLIENT_STALE;
998 svc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
999 struct svc_req *rqst, u_int seq)
1001 gss_buffer_desc signbuf;
1002 gss_buffer_desc mic;
1003 OM_uint32 maj_stat, min_stat;
1006 rpc_gss_log_debug("in svc_rpc_gss_nextverf()");
1009 signbuf.value = &nseq;
1010 signbuf.length = sizeof(nseq);
1012 maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
1015 if (maj_stat != GSS_S_COMPLETE) {
1016 rpc_gss_log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
1017 client->cl_state = CLIENT_STALE;
1021 KASSERT(mic.length <= MAX_AUTH_BYTES,
1022 ("MIC too large for RPCSEC_GSS"));
1024 rqst->rq_verf.oa_flavor = RPCSEC_GSS;
1025 rqst->rq_verf.oa_length = mic.length;
1026 bcopy(mic.value, rqst->rq_verf.oa_base, mic.length);
1028 gss_release_buffer(&min_stat, &mic);
1034 svc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
1036 struct svc_rpc_gss_callback *scb;
1037 rpc_gss_lock_t lock;
1043 * See if we have a callback for this guy.
1046 SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
1047 if (scb->cb_callback.program == rqst->rq_prog
1048 && scb->cb_callback.version == rqst->rq_vers) {
1050 * This one matches. Call the callback and see
1051 * if it wants to veto or something.
1053 lock.locked = FALSE;
1054 lock.raw_cred = &client->cl_rawcred;
1055 cb_res = scb->cb_callback.callback(rqst,
1062 client->cl_state = CLIENT_STALE;
1068 * The callback accepted the connection - it
1069 * is responsible for freeing client->cl_creds
1072 client->cl_creds = GSS_C_NO_CREDENTIAL;
1073 client->cl_locked = lock.locked;
1074 client->cl_cookie = cookie;
1080 * Either no callback exists for this program/version or one
1081 * of the callbacks rejected the connection. We just need to
1082 * clean up the delegated client creds, if any.
1084 if (client->cl_creds) {
1086 gss_release_cred(&min_ver, &client->cl_creds);
1092 svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
1098 sx_xlock(&client->cl_lock);
1099 if (seq <= client->cl_seqlast) {
1101 * The request sequence number is less than
1102 * the largest we have seen so far. If it is
1103 * outside the window or if we have seen a
1104 * request with this sequence before, silently
1107 offset = client->cl_seqlast - seq;
1108 if (offset >= SVC_RPC_GSS_SEQWINDOW) {
1114 if (client->cl_seqmask[word] & (1 << bit)) {
1122 sx_xunlock(&client->cl_lock);
1127 svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
1129 int offset, i, word, bit;
1130 uint32_t carry, newcarry;
1132 sx_xlock(&client->cl_lock);
1133 if (seq > client->cl_seqlast) {
1135 * This request has a sequence number greater
1136 * than any we have seen so far. Advance the
1137 * seq window and set bit zero of the window
1138 * (which corresponds to the new sequence
1141 offset = seq - client->cl_seqlast;
1142 while (offset > 32) {
1143 for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
1145 client->cl_seqmask[i] = client->cl_seqmask[i-1];
1147 client->cl_seqmask[0] = 0;
1151 for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
1152 newcarry = client->cl_seqmask[i] >> (32 - offset);
1153 client->cl_seqmask[i] =
1154 (client->cl_seqmask[i] << offset) | carry;
1157 client->cl_seqmask[0] |= 1;
1158 client->cl_seqlast = seq;
1160 offset = client->cl_seqlast - seq;
1163 client->cl_seqmask[word] |= (1 << bit);
1165 sx_xunlock(&client->cl_lock);
1169 svc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
1174 struct svc_rpc_gss_cookedcred *cc;
1175 struct svc_rpc_gss_client *client;
1176 struct rpc_gss_cred gc;
1177 struct rpc_gss_init_res gr;
1180 enum auth_stat result;
1182 rpc_gss_log_debug("in svc_rpc_gss()");
1184 /* Garbage collect old clients. */
1185 svc_rpc_gss_timeout_clients();
1187 /* Initialize reply. */
1188 rqst->rq_verf = _null_auth;
1190 /* Deserialize client credentials. */
1191 if (rqst->rq_cred.oa_length <= 0)
1192 return (AUTH_BADCRED);
1194 memset(&gc, 0, sizeof(gc));
1196 xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
1197 rqst->rq_cred.oa_length, XDR_DECODE);
1199 if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
1201 return (AUTH_BADCRED);
1207 /* Check version. */
1208 if (gc.gc_version != RPCSEC_GSS_VERSION) {
1209 result = AUTH_BADCRED;
1213 /* Check the proc and find the client (or create it) */
1214 if (gc.gc_proc == RPCSEC_GSS_INIT) {
1215 if (gc.gc_handle.length != 0) {
1216 result = AUTH_BADCRED;
1219 client = svc_rpc_gss_create_client();
1220 refcount_acquire(&client->cl_refs);
1222 struct svc_rpc_gss_clientid *p;
1223 if (gc.gc_handle.length != sizeof(*p)) {
1224 result = AUTH_BADCRED;
1227 p = gc.gc_handle.value;
1228 client = svc_rpc_gss_find_client(p);
1231 * Can't find the client - we may have
1232 * destroyed it - tell the other side to
1235 result = RPCSEC_GSS_CREDPROBLEM;
1239 cc = rqst->rq_clntcred;
1240 cc->cc_client = client;
1241 cc->cc_service = gc.gc_svc;
1242 cc->cc_seq = gc.gc_seq;
1245 * The service and sequence number must be ignored for
1246 * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
1248 if (gc.gc_proc != RPCSEC_GSS_INIT
1249 && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
1251 * Check for sequence number overflow.
1253 if (gc.gc_seq >= MAXSEQ) {
1254 result = RPCSEC_GSS_CTXPROBLEM;
1259 * Check for valid service.
1261 if (gc.gc_svc != rpc_gss_svc_none &&
1262 gc.gc_svc != rpc_gss_svc_integrity &&
1263 gc.gc_svc != rpc_gss_svc_privacy) {
1264 result = AUTH_BADCRED;
1269 /* Handle RPCSEC_GSS control procedure. */
1270 switch (gc.gc_proc) {
1272 case RPCSEC_GSS_INIT:
1273 case RPCSEC_GSS_CONTINUE_INIT:
1274 if (rqst->rq_proc != NULLPROC) {
1275 result = AUTH_REJECTEDCRED;
1279 memset(&gr, 0, sizeof(gr));
1280 if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
1281 result = AUTH_REJECTEDCRED;
1285 if (gr.gr_major == GSS_S_COMPLETE) {
1287 * We borrow the space for the call verf to
1288 * pack our reply verf.
1290 rqst->rq_verf = msg->rm_call.cb_verf;
1291 if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
1292 result = AUTH_REJECTEDCRED;
1296 rqst->rq_verf = _null_auth;
1299 call_stat = svc_sendreply(rqst,
1300 (xdrproc_t) xdr_rpc_gss_init_res,
1303 gss_release_buffer(&min_stat, &gr.gr_token);
1306 result = AUTH_FAILED;
1310 if (gr.gr_major == GSS_S_COMPLETE)
1311 client->cl_state = CLIENT_ESTABLISHED;
1313 result = RPCSEC_GSS_NODISPATCH;
1316 case RPCSEC_GSS_DATA:
1317 case RPCSEC_GSS_DESTROY:
1318 if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
1319 result = RPCSEC_GSS_NODISPATCH;
1323 if (!svc_rpc_gss_validate(client, msg, &qop)) {
1324 result = RPCSEC_GSS_CREDPROBLEM;
1329 * We borrow the space for the call verf to pack our
1332 rqst->rq_verf = msg->rm_call.cb_verf;
1333 if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
1334 result = RPCSEC_GSS_CTXPROBLEM;
1338 svc_rpc_gss_update_seq(client, gc.gc_seq);
1341 * Change the SVCAUTH ops on the request to point at
1342 * our own code so that we can unwrap the arguments
1343 * and wrap the result. The caller will re-set this on
1344 * every request to point to a set of null wrap/unwrap
1345 * methods. Acquire an extra reference to the client
1346 * which will be released by svc_rpc_gss_release()
1347 * after the request has finished processing.
1349 refcount_acquire(&client->cl_refs);
1350 rqst->rq_auth.svc_ah_ops = &svc_auth_gss_ops;
1351 rqst->rq_auth.svc_ah_private = cc;
1353 if (gc.gc_proc == RPCSEC_GSS_DATA) {
1355 * We might be ready to do a callback to the server to
1356 * see if it wants to accept/reject the connection.
1358 sx_xlock(&client->cl_lock);
1359 if (!client->cl_done_callback) {
1360 client->cl_done_callback = TRUE;
1361 client->cl_qop = qop;
1362 client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1363 client->cl_rawcred.mechanism, qop);
1364 if (!svc_rpc_gss_callback(client, rqst)) {
1365 result = AUTH_REJECTEDCRED;
1366 sx_xunlock(&client->cl_lock);
1370 sx_xunlock(&client->cl_lock);
1373 * If the server has locked this client to a
1374 * particular service+qop pair, enforce that
1377 if (client->cl_locked) {
1378 if (client->cl_rawcred.service != gc.gc_svc) {
1379 result = AUTH_FAILED;
1381 } else if (client->cl_qop != qop) {
1382 result = AUTH_BADVERF;
1388 * If the qop changed, look up the new qop
1391 if (client->cl_qop != qop) {
1392 client->cl_qop = qop;
1393 client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1394 client->cl_rawcred.mechanism, qop);
1398 * Make sure we use the right service value
1401 if (client->cl_rawcred.service != gc.gc_svc) {
1402 client->cl_rawcred.service = gc.gc_svc;
1403 svc_rpc_gss_set_flavor(client);
1408 if (rqst->rq_proc != NULLPROC) {
1409 result = AUTH_REJECTEDCRED;
1413 call_stat = svc_sendreply(rqst,
1414 (xdrproc_t) xdr_void, (caddr_t) NULL);
1417 result = AUTH_FAILED;
1421 svc_rpc_gss_forget_client(client);
1423 result = RPCSEC_GSS_NODISPATCH;
1429 result = AUTH_BADCRED;
1434 svc_rpc_gss_release_client(client);
1436 xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
1441 svc_rpc_gss_wrap(SVCAUTH *auth, struct mbuf **mp)
1443 struct svc_rpc_gss_cookedcred *cc;
1444 struct svc_rpc_gss_client *client;
1446 rpc_gss_log_debug("in svc_rpc_gss_wrap()");
1448 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1449 client = cc->cc_client;
1450 if (client->cl_state != CLIENT_ESTABLISHED
1451 || cc->cc_service == rpc_gss_svc_none || *mp == NULL) {
1455 return (xdr_rpc_gss_wrap_data(mp,
1456 client->cl_ctx, client->cl_qop,
1457 cc->cc_service, cc->cc_seq));
1461 svc_rpc_gss_unwrap(SVCAUTH *auth, struct mbuf **mp)
1463 struct svc_rpc_gss_cookedcred *cc;
1464 struct svc_rpc_gss_client *client;
1466 rpc_gss_log_debug("in svc_rpc_gss_unwrap()");
1468 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1469 client = cc->cc_client;
1470 if (client->cl_state != CLIENT_ESTABLISHED
1471 || cc->cc_service == rpc_gss_svc_none) {
1475 return (xdr_rpc_gss_unwrap_data(mp,
1476 client->cl_ctx, client->cl_qop,
1477 cc->cc_service, cc->cc_seq));
1481 svc_rpc_gss_release(SVCAUTH *auth)
1483 struct svc_rpc_gss_cookedcred *cc;
1484 struct svc_rpc_gss_client *client;
1486 rpc_gss_log_debug("in svc_rpc_gss_release()");
1488 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1489 client = cc->cc_client;
1490 svc_rpc_gss_release_client(client);