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) + 1;
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 struct timeval boottime;
508 unsigned long hostid;
510 rpc_gss_log_debug("in svc_rpc_gss_find_client(%d)", id->ci_id);
512 getcredhostid(curthread->td_ucred, &hostid);
513 getboottime(&boottime);
514 if (id->ci_hostid != hostid || id->ci_boottime != boottime.tv_sec)
517 list = &svc_rpc_gss_client_hash[id->ci_id % CLIENT_HASH_SIZE];
518 sx_xlock(&svc_rpc_gss_lock);
519 TAILQ_FOREACH(client, list, cl_link) {
520 if (client->cl_id.ci_id == id->ci_id) {
522 * Move this client to the front of the LRU
525 TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
526 TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client,
528 refcount_acquire(&client->cl_refs);
532 sx_xunlock(&svc_rpc_gss_lock);
537 static struct svc_rpc_gss_client *
538 svc_rpc_gss_create_client(void)
540 struct svc_rpc_gss_client *client;
541 struct svc_rpc_gss_client_list *list;
542 struct timeval boottime;
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 getboottime(&boottime);
554 client->cl_id.ci_boottime = boottime.tv_sec;
555 client->cl_id.ci_id = svc_rpc_gss_next_clientid++;
556 list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
557 sx_xlock(&svc_rpc_gss_lock);
558 TAILQ_INSERT_HEAD(list, client, cl_link);
559 TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client, cl_alllink);
560 svc_rpc_gss_client_count++;
561 sx_xunlock(&svc_rpc_gss_lock);
564 * Start the client off with a short expiration time. We will
565 * try to get a saner value from the client creds later.
567 client->cl_state = CLIENT_NEW;
568 client->cl_locked = FALSE;
569 client->cl_expiration = time_uptime + 5*60;
575 svc_rpc_gss_destroy_client(struct svc_rpc_gss_client *client)
579 rpc_gss_log_debug("in svc_rpc_gss_destroy_client()");
582 gss_delete_sec_context(&min_stat,
583 &client->cl_ctx, GSS_C_NO_BUFFER);
585 if (client->cl_cname)
586 gss_release_name(&min_stat, &client->cl_cname);
588 if (client->cl_rawcred.client_principal)
589 mem_free(client->cl_rawcred.client_principal,
590 sizeof(*client->cl_rawcred.client_principal)
591 + client->cl_rawcred.client_principal->len);
594 crfree(client->cl_cred);
596 sx_destroy(&client->cl_lock);
597 mem_free(client, sizeof(*client));
601 * Drop a reference to a client and free it if that was the last reference.
604 svc_rpc_gss_release_client(struct svc_rpc_gss_client *client)
607 if (!refcount_release(&client->cl_refs))
609 svc_rpc_gss_destroy_client(client);
613 * Remove a client from our global lists.
614 * Must be called with svc_rpc_gss_lock held.
617 svc_rpc_gss_forget_client_locked(struct svc_rpc_gss_client *client)
619 struct svc_rpc_gss_client_list *list;
621 sx_assert(&svc_rpc_gss_lock, SX_XLOCKED);
622 list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
623 TAILQ_REMOVE(list, client, cl_link);
624 TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
625 svc_rpc_gss_client_count--;
629 * Remove a client from our global lists and free it if we can.
632 svc_rpc_gss_forget_client(struct svc_rpc_gss_client *client)
634 struct svc_rpc_gss_client_list *list;
635 struct svc_rpc_gss_client *tclient;
637 list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
638 sx_xlock(&svc_rpc_gss_lock);
639 TAILQ_FOREACH(tclient, list, cl_link) {
641 * Make sure this client has not already been removed
642 * from the lists by svc_rpc_gss_forget_client() or
643 * svc_rpc_gss_forget_client_locked().
645 if (client == tclient) {
646 svc_rpc_gss_forget_client_locked(client);
647 sx_xunlock(&svc_rpc_gss_lock);
648 svc_rpc_gss_release_client(client);
652 sx_xunlock(&svc_rpc_gss_lock);
656 svc_rpc_gss_timeout_clients(void)
658 struct svc_rpc_gss_client *client;
659 time_t now = time_uptime;
661 rpc_gss_log_debug("in svc_rpc_gss_timeout_clients()");
664 * First enforce the max client limit. We keep
665 * svc_rpc_gss_clients in LRU order.
667 sx_xlock(&svc_rpc_gss_lock);
668 client = TAILQ_LAST(&svc_rpc_gss_clients, svc_rpc_gss_client_list);
669 while (svc_rpc_gss_client_count > CLIENT_MAX && client != NULL) {
670 svc_rpc_gss_forget_client_locked(client);
671 sx_xunlock(&svc_rpc_gss_lock);
672 svc_rpc_gss_release_client(client);
673 sx_xlock(&svc_rpc_gss_lock);
674 client = TAILQ_LAST(&svc_rpc_gss_clients,
675 svc_rpc_gss_client_list);
678 TAILQ_FOREACH(client, &svc_rpc_gss_clients, cl_alllink) {
679 if (client->cl_state == CLIENT_STALE
680 || now > client->cl_expiration) {
681 svc_rpc_gss_forget_client_locked(client);
682 sx_xunlock(&svc_rpc_gss_lock);
683 rpc_gss_log_debug("expiring client %p", client);
684 svc_rpc_gss_release_client(client);
685 sx_xlock(&svc_rpc_gss_lock);
689 sx_xunlock(&svc_rpc_gss_lock);
694 * OID<->string routines. These are uuuuugly.
697 gss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
700 unsigned long number;
702 size_t string_length;
707 /* Decoded according to krb5/gssapi_krb5.c */
709 /* First determine the size of the string */
713 cp = (unsigned char *) oid->elements;
714 number = (unsigned long) cp[0];
715 sprintf(numstr, "%ld ", number/40);
716 string_length += strlen(numstr);
717 sprintf(numstr, "%ld ", number%40);
718 string_length += strlen(numstr);
719 for (i=1; i<oid->length; i++) {
720 if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
721 number = (number << 7) | (cp[i] & 0x7f);
726 return(GSS_S_FAILURE);
728 if ((cp[i] & 0x80) == 0) {
729 sprintf(numstr, "%ld ", number);
730 string_length += strlen(numstr);
736 * If we get here, we've calculated the length of "n n n ... n ". Add 4
737 * here for "{ " and "}\0".
740 if ((bp = (char *) mem_alloc(string_length))) {
742 number = (unsigned long) cp[0];
743 sprintf(numstr, "%ld ", number/40);
745 sprintf(numstr, "%ld ", number%40);
748 cp = (unsigned char *) oid->elements;
749 for (i=1; i<oid->length; i++) {
750 number = (number << 7) | (cp[i] & 0x7f);
751 if ((cp[i] & 0x80) == 0) {
752 sprintf(numstr, "%ld ", number);
758 oid_str->length = strlen(bp)+1;
759 oid_str->value = (void *) bp;
761 return(GSS_S_COMPLETE);
764 return(GSS_S_FAILURE);
769 svc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
770 const gss_name_t name)
772 OM_uint32 maj_stat, min_stat;
773 rpc_gss_ucred_t *uc = &client->cl_ucred;
778 uc->gidlist = client->cl_gid_storage;
781 maj_stat = gss_pname_to_unix_cred(&min_stat, name, client->cl_mech,
782 &uc->uid, &uc->gid, &numgroups, &uc->gidlist[0]);
783 if (GSS_ERROR(maj_stat))
786 uc->gidlen = numgroups;
790 svc_rpc_gss_set_flavor(struct svc_rpc_gss_client *client)
792 static gss_OID_desc krb5_mech_oid =
793 {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
796 * Attempt to translate mech type and service into a
797 * 'pseudo flavor'. Hardwire in krb5 support for now.
799 if (kgss_oid_equal(client->cl_mech, &krb5_mech_oid)) {
800 switch (client->cl_rawcred.service) {
801 case rpc_gss_svc_default:
802 case rpc_gss_svc_none:
803 client->cl_rpcflavor = RPCSEC_GSS_KRB5;
805 case rpc_gss_svc_integrity:
806 client->cl_rpcflavor = RPCSEC_GSS_KRB5I;
808 case rpc_gss_svc_privacy:
809 client->cl_rpcflavor = RPCSEC_GSS_KRB5P;
813 client->cl_rpcflavor = RPCSEC_GSS;
818 svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
819 struct svc_req *rqst,
820 struct rpc_gss_init_res *gr,
821 struct rpc_gss_cred *gc)
823 gss_buffer_desc recv_tok;
825 OM_uint32 maj_stat = 0, min_stat = 0, ret_flags;
826 OM_uint32 cred_lifetime;
827 struct svc_rpc_gss_svc_name *sname;
829 rpc_gss_log_debug("in svc_rpc_gss_accept_context()");
831 /* Deserialize arguments. */
832 memset(&recv_tok, 0, sizeof(recv_tok));
834 if (!svc_getargs(rqst,
835 (xdrproc_t) xdr_gss_buffer_desc,
836 (caddr_t) &recv_tok)) {
837 client->cl_state = CLIENT_STALE;
842 * First time round, try all the server names we have until
843 * one matches. Afterwards, stick with that one.
845 sx_xlock(&svc_rpc_gss_lock);
846 if (!client->cl_sname) {
847 SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
848 if (sname->sn_program == rqst->rq_prog
849 && sname->sn_version == rqst->rq_vers) {
851 gr->gr_major = gss_accept_sec_context(
856 GSS_C_NO_CHANNEL_BINDINGS,
864 GSS_S_CREDENTIALS_EXPIRED) {
866 * Either our creds really did
870 if (rpc_gss_acquire_svc_cred(sname))
873 client->cl_sname = sname;
878 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
880 sx_xunlock(&svc_rpc_gss_lock);
884 gr->gr_major = gss_accept_sec_context(
887 client->cl_sname->sn_cred,
889 GSS_C_NO_CHANNEL_BINDINGS,
897 sx_xunlock(&svc_rpc_gss_lock);
899 xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
902 * If we get an error from gss_accept_sec_context, send the
903 * reply anyway so that the client gets a chance to see what
906 if (gr->gr_major != GSS_S_COMPLETE &&
907 gr->gr_major != GSS_S_CONTINUE_NEEDED) {
908 rpc_gss_log_status("accept_sec_context", client->cl_mech,
909 gr->gr_major, gr->gr_minor);
910 client->cl_state = CLIENT_STALE;
914 gr->gr_handle.value = &client->cl_id;
915 gr->gr_handle.length = sizeof(client->cl_id);
916 gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
918 /* Save client info. */
919 client->cl_mech = mech;
920 client->cl_qop = GSS_C_QOP_DEFAULT;
921 client->cl_done_callback = FALSE;
923 if (gr->gr_major == GSS_S_COMPLETE) {
924 gss_buffer_desc export_name;
927 * Change client expiration time to be near when the
928 * client creds expire (or 24 hours if we can't figure
931 if (cred_lifetime == GSS_C_INDEFINITE)
932 cred_lifetime = time_uptime + 24*60*60;
934 client->cl_expiration = time_uptime + cred_lifetime;
937 * Fill in cred details in the rawcred structure.
939 client->cl_rawcred.version = RPCSEC_GSS_VERSION;
940 rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
941 maj_stat = gss_export_name(&min_stat, client->cl_cname,
943 if (maj_stat != GSS_S_COMPLETE) {
944 rpc_gss_log_status("gss_export_name", client->cl_mech,
948 client->cl_rawcred.client_principal =
949 mem_alloc(sizeof(*client->cl_rawcred.client_principal)
950 + export_name.length);
951 client->cl_rawcred.client_principal->len = export_name.length;
952 memcpy(client->cl_rawcred.client_principal->name,
953 export_name.value, export_name.length);
954 gss_release_buffer(&min_stat, &export_name);
955 client->cl_rawcred.svc_principal =
956 client->cl_sname->sn_principal;
957 client->cl_rawcred.service = gc->gc_svc;
960 * Use gss_pname_to_uid to map to unix creds. For
961 * kerberos5, this uses krb5_aname_to_localname.
963 svc_rpc_gss_build_ucred(client, client->cl_cname);
964 svc_rpc_gss_set_flavor(client);
965 gss_release_name(&min_stat, &client->cl_cname);
969 gss_buffer_desc mechname;
971 gss_oid_to_str(&min_stat, mech, &mechname);
973 rpc_gss_log_debug("accepted context for %s with "
974 "<mech %.*s, qop %d, svc %d>",
975 client->cl_rawcred.client_principal->name,
976 mechname.length, (char *)mechname.value,
977 client->cl_qop, client->cl_rawcred.service);
979 gss_release_buffer(&min_stat, &mechname);
987 svc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
988 gss_qop_t *qop, rpc_gss_proc_t gcproc)
990 struct opaque_auth *oa;
991 gss_buffer_desc rpcbuf, checksum;
992 OM_uint32 maj_stat, min_stat;
994 int32_t rpchdr[128 / sizeof(int32_t)];
997 rpc_gss_log_debug("in svc_rpc_gss_validate()");
999 memset(rpchdr, 0, sizeof(rpchdr));
1001 /* Reconstruct RPC header for signing (from xdr_callmsg). */
1003 IXDR_PUT_LONG(buf, msg->rm_xid);
1004 IXDR_PUT_ENUM(buf, msg->rm_direction);
1005 IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
1006 IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
1007 IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
1008 IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
1009 oa = &msg->rm_call.cb_cred;
1010 IXDR_PUT_ENUM(buf, oa->oa_flavor);
1011 IXDR_PUT_LONG(buf, oa->oa_length);
1012 if (oa->oa_length) {
1013 memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
1014 buf += RNDUP(oa->oa_length) / sizeof(int32_t);
1016 rpcbuf.value = rpchdr;
1017 rpcbuf.length = (u_char *)buf - (u_char *)rpchdr;
1019 checksum.value = msg->rm_call.cb_verf.oa_base;
1020 checksum.length = msg->rm_call.cb_verf.oa_length;
1022 maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
1025 if (maj_stat != GSS_S_COMPLETE) {
1026 rpc_gss_log_status("gss_verify_mic", client->cl_mech,
1027 maj_stat, min_stat);
1029 * A bug in some versions of the Linux client generates a
1030 * Destroy operation with a bogus encrypted checksum. Deleting
1031 * the credential handle for that case causes the mount to fail.
1032 * Since the checksum is bogus (gss_verify_mic() failed), it
1033 * doesn't make sense to destroy the handle and not doing so
1034 * fixes the Linux mount.
1036 if (gcproc != RPCSEC_GSS_DESTROY)
1037 client->cl_state = CLIENT_STALE;
1046 svc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
1047 struct svc_req *rqst, u_int seq)
1049 gss_buffer_desc signbuf;
1050 gss_buffer_desc mic;
1051 OM_uint32 maj_stat, min_stat;
1054 rpc_gss_log_debug("in svc_rpc_gss_nextverf()");
1057 signbuf.value = &nseq;
1058 signbuf.length = sizeof(nseq);
1060 maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
1063 if (maj_stat != GSS_S_COMPLETE) {
1064 rpc_gss_log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
1065 client->cl_state = CLIENT_STALE;
1069 KASSERT(mic.length <= MAX_AUTH_BYTES,
1070 ("MIC too large for RPCSEC_GSS"));
1072 rqst->rq_verf.oa_flavor = RPCSEC_GSS;
1073 rqst->rq_verf.oa_length = mic.length;
1074 bcopy(mic.value, rqst->rq_verf.oa_base, mic.length);
1076 gss_release_buffer(&min_stat, &mic);
1082 svc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
1084 struct svc_rpc_gss_callback *scb;
1085 rpc_gss_lock_t lock;
1091 * See if we have a callback for this guy.
1094 SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
1095 if (scb->cb_callback.program == rqst->rq_prog
1096 && scb->cb_callback.version == rqst->rq_vers) {
1098 * This one matches. Call the callback and see
1099 * if it wants to veto or something.
1101 lock.locked = FALSE;
1102 lock.raw_cred = &client->cl_rawcred;
1103 cb_res = scb->cb_callback.callback(rqst,
1110 client->cl_state = CLIENT_STALE;
1116 * The callback accepted the connection - it
1117 * is responsible for freeing client->cl_creds
1120 client->cl_creds = GSS_C_NO_CREDENTIAL;
1121 client->cl_locked = lock.locked;
1122 client->cl_cookie = cookie;
1128 * Either no callback exists for this program/version or one
1129 * of the callbacks rejected the connection. We just need to
1130 * clean up the delegated client creds, if any.
1132 if (client->cl_creds) {
1134 gss_release_cred(&min_ver, &client->cl_creds);
1140 svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
1146 sx_xlock(&client->cl_lock);
1147 if (seq <= client->cl_seqlast) {
1149 * The request sequence number is less than
1150 * the largest we have seen so far. If it is
1151 * outside the window or if we have seen a
1152 * request with this sequence before, silently
1155 offset = client->cl_seqlast - seq;
1156 if (offset >= SVC_RPC_GSS_SEQWINDOW) {
1162 if (client->cl_seqmask[word] & (1 << bit)) {
1170 sx_xunlock(&client->cl_lock);
1175 svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
1177 int offset, i, word, bit;
1178 uint32_t carry, newcarry;
1180 sx_xlock(&client->cl_lock);
1181 if (seq > client->cl_seqlast) {
1183 * This request has a sequence number greater
1184 * than any we have seen so far. Advance the
1185 * seq window and set bit zero of the window
1186 * (which corresponds to the new sequence
1189 offset = seq - client->cl_seqlast;
1190 while (offset > 32) {
1191 for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
1193 client->cl_seqmask[i] = client->cl_seqmask[i-1];
1195 client->cl_seqmask[0] = 0;
1199 for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
1200 newcarry = client->cl_seqmask[i] >> (32 - offset);
1201 client->cl_seqmask[i] =
1202 (client->cl_seqmask[i] << offset) | carry;
1205 client->cl_seqmask[0] |= 1;
1206 client->cl_seqlast = seq;
1208 offset = client->cl_seqlast - seq;
1211 client->cl_seqmask[word] |= (1 << bit);
1213 sx_xunlock(&client->cl_lock);
1217 svc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
1222 struct svc_rpc_gss_cookedcred *cc;
1223 struct svc_rpc_gss_client *client;
1224 struct rpc_gss_cred gc;
1225 struct rpc_gss_init_res gr;
1228 enum auth_stat result;
1230 rpc_gss_log_debug("in svc_rpc_gss()");
1232 /* Garbage collect old clients. */
1233 svc_rpc_gss_timeout_clients();
1235 /* Initialize reply. */
1236 rqst->rq_verf = _null_auth;
1238 /* Deserialize client credentials. */
1239 if (rqst->rq_cred.oa_length <= 0)
1240 return (AUTH_BADCRED);
1242 memset(&gc, 0, sizeof(gc));
1244 xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
1245 rqst->rq_cred.oa_length, XDR_DECODE);
1247 if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
1249 return (AUTH_BADCRED);
1255 /* Check version. */
1256 if (gc.gc_version != RPCSEC_GSS_VERSION) {
1257 result = AUTH_BADCRED;
1261 /* Check the proc and find the client (or create it) */
1262 if (gc.gc_proc == RPCSEC_GSS_INIT) {
1263 if (gc.gc_handle.length != 0) {
1264 result = AUTH_BADCRED;
1267 client = svc_rpc_gss_create_client();
1268 refcount_acquire(&client->cl_refs);
1270 struct svc_rpc_gss_clientid *p;
1271 if (gc.gc_handle.length != sizeof(*p)) {
1272 result = AUTH_BADCRED;
1275 p = gc.gc_handle.value;
1276 client = svc_rpc_gss_find_client(p);
1279 * Can't find the client - we may have
1280 * destroyed it - tell the other side to
1283 result = RPCSEC_GSS_CREDPROBLEM;
1287 cc = rqst->rq_clntcred;
1288 cc->cc_client = client;
1289 cc->cc_service = gc.gc_svc;
1290 cc->cc_seq = gc.gc_seq;
1293 * The service and sequence number must be ignored for
1294 * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
1296 if (gc.gc_proc != RPCSEC_GSS_INIT
1297 && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
1299 * Check for sequence number overflow.
1301 if (gc.gc_seq >= MAXSEQ) {
1302 result = RPCSEC_GSS_CTXPROBLEM;
1307 * Check for valid service.
1309 if (gc.gc_svc != rpc_gss_svc_none &&
1310 gc.gc_svc != rpc_gss_svc_integrity &&
1311 gc.gc_svc != rpc_gss_svc_privacy) {
1312 result = AUTH_BADCRED;
1317 /* Handle RPCSEC_GSS control procedure. */
1318 switch (gc.gc_proc) {
1320 case RPCSEC_GSS_INIT:
1321 case RPCSEC_GSS_CONTINUE_INIT:
1322 if (rqst->rq_proc != NULLPROC) {
1323 result = AUTH_REJECTEDCRED;
1327 memset(&gr, 0, sizeof(gr));
1328 if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
1329 result = AUTH_REJECTEDCRED;
1333 if (gr.gr_major == GSS_S_COMPLETE) {
1335 * We borrow the space for the call verf to
1336 * pack our reply verf.
1338 rqst->rq_verf = msg->rm_call.cb_verf;
1339 if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
1340 result = AUTH_REJECTEDCRED;
1344 rqst->rq_verf = _null_auth;
1347 call_stat = svc_sendreply(rqst,
1348 (xdrproc_t) xdr_rpc_gss_init_res,
1351 gss_release_buffer(&min_stat, &gr.gr_token);
1354 result = AUTH_FAILED;
1358 if (gr.gr_major == GSS_S_COMPLETE)
1359 client->cl_state = CLIENT_ESTABLISHED;
1361 result = RPCSEC_GSS_NODISPATCH;
1364 case RPCSEC_GSS_DATA:
1365 case RPCSEC_GSS_DESTROY:
1366 if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
1367 result = RPCSEC_GSS_NODISPATCH;
1371 if (!svc_rpc_gss_validate(client, msg, &qop, gc.gc_proc)) {
1372 result = RPCSEC_GSS_CREDPROBLEM;
1377 * We borrow the space for the call verf to pack our
1380 rqst->rq_verf = msg->rm_call.cb_verf;
1381 if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
1382 result = RPCSEC_GSS_CTXPROBLEM;
1386 svc_rpc_gss_update_seq(client, gc.gc_seq);
1389 * Change the SVCAUTH ops on the request to point at
1390 * our own code so that we can unwrap the arguments
1391 * and wrap the result. The caller will re-set this on
1392 * every request to point to a set of null wrap/unwrap
1393 * methods. Acquire an extra reference to the client
1394 * which will be released by svc_rpc_gss_release()
1395 * after the request has finished processing.
1397 refcount_acquire(&client->cl_refs);
1398 rqst->rq_auth.svc_ah_ops = &svc_auth_gss_ops;
1399 rqst->rq_auth.svc_ah_private = cc;
1401 if (gc.gc_proc == RPCSEC_GSS_DATA) {
1403 * We might be ready to do a callback to the server to
1404 * see if it wants to accept/reject the connection.
1406 sx_xlock(&client->cl_lock);
1407 if (!client->cl_done_callback) {
1408 client->cl_done_callback = TRUE;
1409 client->cl_qop = qop;
1410 client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1411 client->cl_rawcred.mechanism, qop);
1412 if (!svc_rpc_gss_callback(client, rqst)) {
1413 result = AUTH_REJECTEDCRED;
1414 sx_xunlock(&client->cl_lock);
1418 sx_xunlock(&client->cl_lock);
1421 * If the server has locked this client to a
1422 * particular service+qop pair, enforce that
1425 if (client->cl_locked) {
1426 if (client->cl_rawcred.service != gc.gc_svc) {
1427 result = AUTH_FAILED;
1429 } else if (client->cl_qop != qop) {
1430 result = AUTH_BADVERF;
1436 * If the qop changed, look up the new qop
1439 if (client->cl_qop != qop) {
1440 client->cl_qop = qop;
1441 client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1442 client->cl_rawcred.mechanism, qop);
1446 * Make sure we use the right service value
1449 if (client->cl_rawcred.service != gc.gc_svc) {
1450 client->cl_rawcred.service = gc.gc_svc;
1451 svc_rpc_gss_set_flavor(client);
1456 if (rqst->rq_proc != NULLPROC) {
1457 result = AUTH_REJECTEDCRED;
1461 call_stat = svc_sendreply(rqst,
1462 (xdrproc_t) xdr_void, (caddr_t) NULL);
1465 result = AUTH_FAILED;
1469 svc_rpc_gss_forget_client(client);
1471 result = RPCSEC_GSS_NODISPATCH;
1477 result = AUTH_BADCRED;
1482 svc_rpc_gss_release_client(client);
1484 xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
1489 svc_rpc_gss_wrap(SVCAUTH *auth, struct mbuf **mp)
1491 struct svc_rpc_gss_cookedcred *cc;
1492 struct svc_rpc_gss_client *client;
1494 rpc_gss_log_debug("in svc_rpc_gss_wrap()");
1496 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1497 client = cc->cc_client;
1498 if (client->cl_state != CLIENT_ESTABLISHED
1499 || cc->cc_service == rpc_gss_svc_none || *mp == NULL) {
1503 return (xdr_rpc_gss_wrap_data(mp,
1504 client->cl_ctx, client->cl_qop,
1505 cc->cc_service, cc->cc_seq));
1509 svc_rpc_gss_unwrap(SVCAUTH *auth, struct mbuf **mp)
1511 struct svc_rpc_gss_cookedcred *cc;
1512 struct svc_rpc_gss_client *client;
1514 rpc_gss_log_debug("in svc_rpc_gss_unwrap()");
1516 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1517 client = cc->cc_client;
1518 if (client->cl_state != CLIENT_ESTABLISHED
1519 || cc->cc_service == rpc_gss_svc_none) {
1523 return (xdr_rpc_gss_unwrap_data(mp,
1524 client->cl_ctx, client->cl_qop,
1525 cc->cc_service, cc->cc_seq));
1529 svc_rpc_gss_release(SVCAUTH *auth)
1531 struct svc_rpc_gss_cookedcred *cc;
1532 struct svc_rpc_gss_client *client;
1534 rpc_gss_log_debug("in svc_rpc_gss_release()");
1536 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1537 client = cc->cc_client;
1538 svc_rpc_gss_release_client(client);