]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - sys/rpc/rpcsec_gss/svc_rpcsec_gss.c
- Copy stable/10@296371 to releng/10.3 in preparation for 10.3-RC1
[FreeBSD/releng/10.3.git] / sys / rpc / rpcsec_gss / svc_rpcsec_gss.c
1 /*-
2  * Copyright (c) 2008 Doug Rabson
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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.
13  *
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
24  * SUCH DAMAGE.
25  */
26 /*
27   svc_rpcsec_gss.c
28   
29   Copyright (c) 2000 The Regents of the University of Michigan.
30   All rights reserved.
31
32   Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
33   All rights reserved, all wrongs reversed.
34
35   Redistribution and use in source and binary forms, with or without
36   modification, are permitted provided that the following conditions
37   are met:
38
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.
47
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.
59
60   $Id: svc_auth_gss.c,v 1.27 2002/01/15 15:43:00 andros Exp $
61  */
62
63 #include <sys/cdefs.h>
64 __FBSDID("$FreeBSD$");
65
66 #include <sys/param.h>
67 #include <sys/systm.h>
68 #include <sys/jail.h>
69 #include <sys/kernel.h>
70 #include <sys/kobj.h>
71 #include <sys/lock.h>
72 #include <sys/malloc.h>
73 #include <sys/mbuf.h>
74 #include <sys/mutex.h>
75 #include <sys/proc.h>
76 #include <sys/sx.h>
77 #include <sys/ucred.h>
78
79 #include <rpc/rpc.h>
80 #include <rpc/rpcsec_gss.h>
81
82 #include "rpcsec_gss_int.h"
83
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 *);
89
90 static struct svc_auth_ops svc_auth_gss_ops = {
91         svc_rpc_gss_wrap,
92         svc_rpc_gss_unwrap,
93         svc_rpc_gss_release,
94 };
95
96 struct sx svc_rpc_gss_lock;
97
98 struct svc_rpc_gss_callback {
99         SLIST_ENTRY(svc_rpc_gss_callback) cb_link;
100         rpc_gss_callback_t      cb_callback;
101 };
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);
104
105 struct svc_rpc_gss_svc_name {
106         SLIST_ENTRY(svc_rpc_gss_svc_name) sn_link;
107         char                    *sn_principal;
108         gss_OID                 sn_mech;
109         u_int                   sn_req_time;
110         gss_cred_id_t           sn_cred;
111         u_int                   sn_program;
112         u_int                   sn_version;
113 };
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);
116
117 enum svc_rpc_gss_client_state {
118         CLIENT_NEW,                             /* still authenticating */
119         CLIENT_ESTABLISHED,                     /* context established */
120         CLIENT_STALE                            /* garbage to collect */
121 };
122
123 #define SVC_RPC_GSS_SEQWINDOW   128
124
125 struct svc_rpc_gss_clientid {
126         unsigned long           ci_hostid;
127         uint32_t                ci_boottime;
128         uint32_t                ci_id;
129 };
130
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;
135         struct sx               cl_lock;
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 */
155 };
156 TAILQ_HEAD(svc_rpc_gss_client_list, svc_rpc_gss_client);
157
158 /*
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).
162  */
163 struct svc_rpc_gss_cookedcred {
164         struct svc_rpc_gss_client *cc_client;
165         rpc_gss_service_t       cc_service;
166         uint32_t                cc_seq;
167 };
168
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;
175
176 static void
177 svc_rpc_gss_init(void *arg)
178 {
179         int i;
180
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");
186 }
187 SYSINIT(svc_rpc_gss_init, SI_SUB_KMEM, SI_ORDER_ANY, svc_rpc_gss_init, NULL);
188
189 bool_t
190 rpc_gss_set_callback(rpc_gss_callback_t *cb)
191 {
192         struct svc_rpc_gss_callback *scb;
193
194         scb = mem_alloc(sizeof(struct svc_rpc_gss_callback));
195         if (!scb) {
196                 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
197                 return (FALSE);
198         }
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);
203
204         return (TRUE);
205 }
206
207 void
208 rpc_gss_clear_callback(rpc_gss_callback_t *cb)
209 {
210         struct svc_rpc_gss_callback *scb;
211
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));
221                         return;
222                 }
223         }
224         sx_xunlock(&svc_rpc_gss_lock);
225 }
226
227 static bool_t
228 rpc_gss_acquire_svc_cred(struct svc_rpc_gss_svc_name *sname)
229 {
230         OM_uint32               maj_stat, min_stat;
231         gss_buffer_desc         namebuf;
232         gss_name_t              name;
233         gss_OID_set_desc        oid_set;
234
235         oid_set.count = 1;
236         oid_set.elements = sname->sn_mech;
237
238         namebuf.value = (void *) sname->sn_principal;
239         namebuf.length = strlen(sname->sn_principal);
240
241         maj_stat = gss_import_name(&min_stat, &namebuf,
242                                    GSS_C_NT_HOSTBASED_SERVICE, &name);
243         if (maj_stat != GSS_S_COMPLETE)
244                 return (FALSE);
245
246         if (sname->sn_cred != GSS_C_NO_CREDENTIAL)
247                 gss_release_cred(&min_stat, &sname->sn_cred);
248
249         maj_stat = gss_acquire_cred(&min_stat, name,
250             sname->sn_req_time, &oid_set, GSS_C_ACCEPT, &sname->sn_cred,
251             NULL, NULL);
252         if (maj_stat != GSS_S_COMPLETE) {
253                 gss_release_name(&min_stat, &name);
254                 return (FALSE);
255         }
256         gss_release_name(&min_stat, &name);
257
258         return (TRUE);
259 }
260
261 bool_t
262 rpc_gss_set_svc_name(const char *principal, const char *mechanism,
263     u_int req_time, u_int program, u_int version)
264 {
265         struct svc_rpc_gss_svc_name *sname;
266         gss_OID                 mech_oid;
267
268         if (!rpc_gss_mech_to_oid(mechanism, &mech_oid))
269                 return (FALSE);
270
271         sname = mem_alloc(sizeof(*sname));
272         if (!sname)
273                 return (FALSE);
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;
280
281         if (!rpc_gss_acquire_svc_cred(sname)) {
282                 free(sname->sn_principal, M_RPC);
283                 mem_free(sname, sizeof(*sname));
284                 return (FALSE);
285         }
286
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);
290
291         return (TRUE);
292 }
293
294 void
295 rpc_gss_clear_svc_name(u_int program, u_int version)
296 {
297         OM_uint32               min_stat;
298         struct svc_rpc_gss_svc_name *sname;
299
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));
310                         return;
311                 }
312         }
313         sx_xunlock(&svc_rpc_gss_lock);
314 }
315
316 bool_t
317 rpc_gss_get_principal_name(rpc_gss_principal_t *principal,
318     const char *mech, const char *name, const char *node, const char *domain)
319 {
320         OM_uint32               maj_stat, min_stat;
321         gss_OID                 mech_oid;
322         size_t                  namelen;
323         gss_buffer_desc         buf;
324         gss_name_t              gss_name, gss_mech_name;
325         rpc_gss_principal_t     result;
326
327         if (!rpc_gss_mech_to_oid(mech, &mech_oid))
328                 return (FALSE);
329
330         /*
331          * Construct a gss_buffer containing the full name formatted
332          * as "name/node@domain" where node and domain are optional.
333          */
334         namelen = strlen(name);
335         if (node) {
336                 namelen += strlen(node) + 1;
337         }
338         if (domain) {
339                 namelen += strlen(domain) + 1;
340         }
341
342         buf.value = mem_alloc(namelen);
343         buf.length = namelen;
344         strcpy((char *) buf.value, name);
345         if (node) {
346                 strcat((char *) buf.value, "/");
347                 strcat((char *) buf.value, node);
348         }
349         if (domain) {
350                 strcat((char *) buf.value, "@");
351                 strcat((char *) buf.value, domain);
352         }
353
354         /*
355          * Convert that to a gss_name_t and then convert that to a
356          * mechanism name in the selected mechanism.
357          */
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);
363                 return (FALSE);
364         }
365         maj_stat = gss_canonicalize_name(&min_stat, gss_name, mech_oid,
366             &gss_mech_name);
367         if (maj_stat != GSS_S_COMPLETE) {
368                 rpc_gss_log_status("gss_canonicalize_name", mech_oid, maj_stat,
369                     min_stat);
370                 gss_release_name(&min_stat, &gss_name);
371                 return (FALSE);
372         }
373         gss_release_name(&min_stat, &gss_name);
374
375         /*
376          * Export the mechanism name and use that to construct the
377          * rpc_gss_principal_t result.
378          */
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);
383                 return (FALSE);
384         }
385         gss_release_name(&min_stat, &gss_mech_name);
386
387         result = mem_alloc(sizeof(int) + buf.length);
388         if (!result) {
389                 gss_release_buffer(&min_stat, &buf);
390                 return (FALSE);
391         }
392         result->len = buf.length;
393         memcpy(result->name, buf.value, buf.length);
394         gss_release_buffer(&min_stat, &buf);
395
396         *principal = result;
397         return (TRUE);
398 }
399
400 bool_t
401 rpc_gss_getcred(struct svc_req *req, rpc_gss_rawcred_t **rcred,
402     rpc_gss_ucred_t **ucred, void **cookie)
403 {
404         struct svc_rpc_gss_cookedcred *cc;
405         struct svc_rpc_gss_client *client;
406
407         if (req->rq_cred.oa_flavor != RPCSEC_GSS)
408                 return (FALSE);
409
410         cc = req->rq_clntcred;
411         client = cc->cc_client;
412         if (rcred)
413                 *rcred = &client->cl_rawcred;
414         if (ucred)
415                 *ucred = &client->cl_ucred;
416         if (cookie)
417                 *cookie = client->cl_cookie;
418         return (TRUE);
419 }
420
421 /*
422  * This simpler interface is used by svc_getcred to copy the cred data
423  * into a kernel cred structure.
424  */
425 static int
426 rpc_gss_svc_getcred(struct svc_req *req, struct ucred **crp, int *flavorp)
427 {
428         struct ucred *cr;
429         struct svc_rpc_gss_cookedcred *cc;
430         struct svc_rpc_gss_client *client;
431         rpc_gss_ucred_t *uc;
432
433         if (req->rq_cred.oa_flavor != RPCSEC_GSS)
434                 return (FALSE);
435
436         cc = req->rq_clntcred;
437         client = cc->cc_client;
438
439         if (flavorp)
440                 *flavorp = client->cl_rpcflavor;
441
442         if (client->cl_cred) {
443                 *crp = crhold(client->cl_cred);
444                 return (TRUE);
445         }
446
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);
454         *crp = crhold(cr);
455
456         return (TRUE);
457 }
458
459 int
460 rpc_gss_svc_max_data_length(struct svc_req *req, int max_tp_unit_len)
461 {
462         struct svc_rpc_gss_cookedcred *cc = req->rq_clntcred;
463         struct svc_rpc_gss_client *client = cc->cc_client;
464         int                     want_conf;
465         OM_uint32               max;
466         OM_uint32               maj_stat, min_stat;
467         int                     result;
468
469         switch (client->cl_rawcred.service) {
470         case rpc_gss_svc_none:
471                 return (max_tp_unit_len);
472                 break;
473
474         case rpc_gss_svc_default:
475         case rpc_gss_svc_integrity:
476                 want_conf = FALSE;
477                 break;
478
479         case rpc_gss_svc_privacy:
480                 want_conf = TRUE;
481                 break;
482
483         default:
484                 return (0);
485         }
486
487         maj_stat = gss_wrap_size_limit(&min_stat, client->cl_ctx, want_conf,
488             client->cl_qop, max_tp_unit_len, &max);
489
490         if (maj_stat == GSS_S_COMPLETE) {
491                 result = (int) max;
492                 if (result < 0)
493                         result = 0;
494                 return (result);
495         } else {
496                 rpc_gss_log_status("gss_wrap_size_limit", client->cl_mech,
497                     maj_stat, min_stat);
498                 return (0);
499         }
500 }
501
502 static struct svc_rpc_gss_client *
503 svc_rpc_gss_find_client(struct svc_rpc_gss_clientid *id)
504 {
505         struct svc_rpc_gss_client *client;
506         struct svc_rpc_gss_client_list *list;
507         unsigned long hostid;
508
509         rpc_gss_log_debug("in svc_rpc_gss_find_client(%d)", id->ci_id);
510
511         getcredhostid(curthread->td_ucred, &hostid);
512         if (id->ci_hostid != hostid || id->ci_boottime != boottime.tv_sec)
513                 return (NULL);
514
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) {
519                         /*
520                          * Move this client to the front of the LRU
521                          * list.
522                          */
523                         TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
524                         TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client,
525                             cl_alllink);
526                         refcount_acquire(&client->cl_refs);
527                         break;
528                 }
529         }
530         sx_xunlock(&svc_rpc_gss_lock);
531
532         return (client);
533 }
534
535 static struct svc_rpc_gss_client *
536 svc_rpc_gss_create_client(void)
537 {
538         struct svc_rpc_gss_client *client;
539         struct svc_rpc_gss_client_list *list;
540         unsigned long hostid;
541
542         rpc_gss_log_debug("in svc_rpc_gss_create_client()");
543
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);
558
559         /*
560          * Start the client off with a short expiration time. We will
561          * try to get a saner value from the client creds later.
562          */
563         client->cl_state = CLIENT_NEW;
564         client->cl_locked = FALSE;
565         client->cl_expiration = time_uptime + 5*60;
566
567         return (client);
568 }
569
570 static void
571 svc_rpc_gss_destroy_client(struct svc_rpc_gss_client *client)
572 {
573         OM_uint32 min_stat;
574
575         rpc_gss_log_debug("in svc_rpc_gss_destroy_client()");
576
577         if (client->cl_ctx)
578                 gss_delete_sec_context(&min_stat,
579                     &client->cl_ctx, GSS_C_NO_BUFFER);
580
581         if (client->cl_cname)
582                 gss_release_name(&min_stat, &client->cl_cname);
583
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);
588
589         if (client->cl_cred)
590                 crfree(client->cl_cred);
591
592         sx_destroy(&client->cl_lock);
593         mem_free(client, sizeof(*client));
594 }
595
596 /*
597  * Drop a reference to a client and free it if that was the last reference.
598  */
599 static void
600 svc_rpc_gss_release_client(struct svc_rpc_gss_client *client)
601 {
602
603         if (!refcount_release(&client->cl_refs))
604                 return;
605         svc_rpc_gss_destroy_client(client);
606 }
607
608 /*
609  * Remove a client from our global lists.
610  * Must be called with svc_rpc_gss_lock held.
611  */
612 static void
613 svc_rpc_gss_forget_client_locked(struct svc_rpc_gss_client *client)
614 {
615         struct svc_rpc_gss_client_list *list;
616
617         sx_assert(&svc_rpc_gss_lock, SX_XLOCKED);
618         list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
619         TAILQ_REMOVE(list, client, cl_link);
620         TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
621         svc_rpc_gss_client_count--;
622 }
623
624 /*
625  * Remove a client from our global lists and free it if we can.
626  */
627 static void
628 svc_rpc_gss_forget_client(struct svc_rpc_gss_client *client)
629 {
630         struct svc_rpc_gss_client_list *list;
631         struct svc_rpc_gss_client *tclient;
632
633         list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
634         sx_xlock(&svc_rpc_gss_lock);
635         TAILQ_FOREACH(tclient, list, cl_link) {
636                 /*
637                  * Make sure this client has not already been removed
638                  * from the lists by svc_rpc_gss_forget_client() or
639                  * svc_rpc_gss_forget_client_locked().
640                  */
641                 if (client == tclient) {
642                         svc_rpc_gss_forget_client_locked(client);
643                         sx_xunlock(&svc_rpc_gss_lock);
644                         svc_rpc_gss_release_client(client);
645                         return;
646                 }
647         }
648         sx_xunlock(&svc_rpc_gss_lock);
649 }
650
651 static void
652 svc_rpc_gss_timeout_clients(void)
653 {
654         struct svc_rpc_gss_client *client;
655         time_t now = time_uptime;
656
657         rpc_gss_log_debug("in svc_rpc_gss_timeout_clients()");
658
659         /*
660          * First enforce the max client limit. We keep
661          * svc_rpc_gss_clients in LRU order.
662          */
663         sx_xlock(&svc_rpc_gss_lock);
664         client = TAILQ_LAST(&svc_rpc_gss_clients, svc_rpc_gss_client_list);
665         while (svc_rpc_gss_client_count > CLIENT_MAX && client != NULL) {
666                 svc_rpc_gss_forget_client_locked(client);
667                 sx_xunlock(&svc_rpc_gss_lock);
668                 svc_rpc_gss_release_client(client);
669                 sx_xlock(&svc_rpc_gss_lock);
670                 client = TAILQ_LAST(&svc_rpc_gss_clients,
671                     svc_rpc_gss_client_list);
672         }
673 again:
674         TAILQ_FOREACH(client, &svc_rpc_gss_clients, cl_alllink) {
675                 if (client->cl_state == CLIENT_STALE
676                     || now > client->cl_expiration) {
677                         svc_rpc_gss_forget_client_locked(client);
678                         sx_xunlock(&svc_rpc_gss_lock);
679                         rpc_gss_log_debug("expiring client %p", client);
680                         svc_rpc_gss_release_client(client);
681                         sx_xlock(&svc_rpc_gss_lock);
682                         goto again;
683                 }
684         }
685         sx_xunlock(&svc_rpc_gss_lock);
686 }
687
688 #ifdef DEBUG
689 /*
690  * OID<->string routines.  These are uuuuugly.
691  */
692 static OM_uint32
693 gss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
694 {
695         char            numstr[128];
696         unsigned long   number;
697         int             numshift;
698         size_t          string_length;
699         size_t          i;
700         unsigned char   *cp;
701         char            *bp;
702
703         /* Decoded according to krb5/gssapi_krb5.c */
704
705         /* First determine the size of the string */
706         string_length = 0;
707         number = 0;
708         numshift = 0;
709         cp = (unsigned char *) oid->elements;
710         number = (unsigned long) cp[0];
711         sprintf(numstr, "%ld ", number/40);
712         string_length += strlen(numstr);
713         sprintf(numstr, "%ld ", number%40);
714         string_length += strlen(numstr);
715         for (i=1; i<oid->length; i++) {
716                 if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
717                         number = (number << 7) | (cp[i] & 0x7f);
718                         numshift += 7;
719                 }
720                 else {
721                         *minor_status = 0;
722                         return(GSS_S_FAILURE);
723                 }
724                 if ((cp[i] & 0x80) == 0) {
725                         sprintf(numstr, "%ld ", number);
726                         string_length += strlen(numstr);
727                         number = 0;
728                         numshift = 0;
729                 }
730         }
731         /*
732          * If we get here, we've calculated the length of "n n n ... n ".  Add 4
733          * here for "{ " and "}\0".
734          */
735         string_length += 4;
736         if ((bp = (char *) mem_alloc(string_length))) {
737                 strcpy(bp, "{ ");
738                 number = (unsigned long) cp[0];
739                 sprintf(numstr, "%ld ", number/40);
740                 strcat(bp, numstr);
741                 sprintf(numstr, "%ld ", number%40);
742                 strcat(bp, numstr);
743                 number = 0;
744                 cp = (unsigned char *) oid->elements;
745                 for (i=1; i<oid->length; i++) {
746                         number = (number << 7) | (cp[i] & 0x7f);
747                         if ((cp[i] & 0x80) == 0) {
748                                 sprintf(numstr, "%ld ", number);
749                                 strcat(bp, numstr);
750                                 number = 0;
751                         }
752                 }
753                 strcat(bp, "}");
754                 oid_str->length = strlen(bp)+1;
755                 oid_str->value = (void *) bp;
756                 *minor_status = 0;
757                 return(GSS_S_COMPLETE);
758         }
759         *minor_status = 0;
760         return(GSS_S_FAILURE);
761 }
762 #endif
763
764 static void
765 svc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
766     const gss_name_t name)
767 {
768         OM_uint32               maj_stat, min_stat;
769         rpc_gss_ucred_t         *uc = &client->cl_ucred;
770         int                     numgroups;
771
772         uc->uid = 65534;
773         uc->gid = 65534;
774         uc->gidlist = client->cl_gid_storage;
775
776         numgroups = NGROUPS;
777         maj_stat = gss_pname_to_unix_cred(&min_stat, name, client->cl_mech,
778             &uc->uid, &uc->gid, &numgroups, &uc->gidlist[0]);
779         if (GSS_ERROR(maj_stat))
780                 uc->gidlen = 0;
781         else
782                 uc->gidlen = numgroups;
783 }
784
785 static void
786 svc_rpc_gss_set_flavor(struct svc_rpc_gss_client *client)
787 {
788         static gss_OID_desc krb5_mech_oid =
789                 {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
790
791         /*
792          * Attempt to translate mech type and service into a
793          * 'pseudo flavor'. Hardwire in krb5 support for now.
794          */
795         if (kgss_oid_equal(client->cl_mech, &krb5_mech_oid)) {
796                 switch (client->cl_rawcred.service) {
797                 case rpc_gss_svc_default:
798                 case rpc_gss_svc_none:
799                         client->cl_rpcflavor = RPCSEC_GSS_KRB5;
800                         break;
801                 case rpc_gss_svc_integrity:
802                         client->cl_rpcflavor = RPCSEC_GSS_KRB5I;
803                         break;
804                 case rpc_gss_svc_privacy:
805                         client->cl_rpcflavor = RPCSEC_GSS_KRB5P;
806                         break;
807                 }
808         } else {
809                 client->cl_rpcflavor = RPCSEC_GSS;
810         }
811 }
812
813 static bool_t
814 svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
815                                struct svc_req *rqst,
816                                struct rpc_gss_init_res *gr,
817                                struct rpc_gss_cred *gc)
818 {
819         gss_buffer_desc         recv_tok;
820         gss_OID                 mech;
821         OM_uint32               maj_stat = 0, min_stat = 0, ret_flags;
822         OM_uint32               cred_lifetime;
823         struct svc_rpc_gss_svc_name *sname;
824
825         rpc_gss_log_debug("in svc_rpc_gss_accept_context()");
826         
827         /* Deserialize arguments. */
828         memset(&recv_tok, 0, sizeof(recv_tok));
829         
830         if (!svc_getargs(rqst,
831                 (xdrproc_t) xdr_gss_buffer_desc,
832                 (caddr_t) &recv_tok)) {
833                 client->cl_state = CLIENT_STALE;
834                 return (FALSE);
835         }
836
837         /*
838          * First time round, try all the server names we have until
839          * one matches. Afterwards, stick with that one.
840          */
841         sx_xlock(&svc_rpc_gss_lock);
842         if (!client->cl_sname) {
843                 SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
844                         if (sname->sn_program == rqst->rq_prog
845                             && sname->sn_version == rqst->rq_vers) {
846                         retry:
847                                 gr->gr_major = gss_accept_sec_context(
848                                         &gr->gr_minor,
849                                         &client->cl_ctx,
850                                         sname->sn_cred,
851                                         &recv_tok,
852                                         GSS_C_NO_CHANNEL_BINDINGS,
853                                         &client->cl_cname,
854                                         &mech,
855                                         &gr->gr_token,
856                                         &ret_flags,
857                                         &cred_lifetime,
858                                         &client->cl_creds);
859                                 if (gr->gr_major == 
860                                     GSS_S_CREDENTIALS_EXPIRED) {
861                                         /*
862                                          * Either our creds really did
863                                          * expire or gssd was
864                                          * restarted.
865                                          */
866                                         if (rpc_gss_acquire_svc_cred(sname))
867                                                 goto retry;
868                                 }
869                                 client->cl_sname = sname;
870                                 break;
871                         }
872                 }
873                 if (!sname) {
874                         xdr_free((xdrproc_t) xdr_gss_buffer_desc,
875                             (char *) &recv_tok);
876                         sx_xunlock(&svc_rpc_gss_lock);
877                         return (FALSE);
878                 }
879         } else {
880                 gr->gr_major = gss_accept_sec_context(
881                         &gr->gr_minor,
882                         &client->cl_ctx,
883                         client->cl_sname->sn_cred,
884                         &recv_tok,
885                         GSS_C_NO_CHANNEL_BINDINGS,
886                         &client->cl_cname,
887                         &mech,
888                         &gr->gr_token,
889                         &ret_flags,
890                         &cred_lifetime,
891                         NULL);
892         }
893         sx_xunlock(&svc_rpc_gss_lock);
894         
895         xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
896
897         /*
898          * If we get an error from gss_accept_sec_context, send the
899          * reply anyway so that the client gets a chance to see what
900          * is wrong.
901          */
902         if (gr->gr_major != GSS_S_COMPLETE &&
903             gr->gr_major != GSS_S_CONTINUE_NEEDED) {
904                 rpc_gss_log_status("accept_sec_context", client->cl_mech,
905                     gr->gr_major, gr->gr_minor);
906                 client->cl_state = CLIENT_STALE;
907                 return (TRUE);
908         }
909
910         gr->gr_handle.value = &client->cl_id;
911         gr->gr_handle.length = sizeof(client->cl_id);
912         gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
913         
914         /* Save client info. */
915         client->cl_mech = mech;
916         client->cl_qop = GSS_C_QOP_DEFAULT;
917         client->cl_done_callback = FALSE;
918
919         if (gr->gr_major == GSS_S_COMPLETE) {
920                 gss_buffer_desc export_name;
921
922                 /*
923                  * Change client expiration time to be near when the
924                  * client creds expire (or 24 hours if we can't figure
925                  * that out).
926                  */
927                 if (cred_lifetime == GSS_C_INDEFINITE)
928                         cred_lifetime = time_uptime + 24*60*60;
929
930                 client->cl_expiration = time_uptime + cred_lifetime;
931
932                 /*
933                  * Fill in cred details in the rawcred structure.
934                  */
935                 client->cl_rawcred.version = RPCSEC_GSS_VERSION;
936                 rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
937                 maj_stat = gss_export_name(&min_stat, client->cl_cname,
938                     &export_name);
939                 if (maj_stat != GSS_S_COMPLETE) {
940                         rpc_gss_log_status("gss_export_name", client->cl_mech,
941                             maj_stat, min_stat);
942                         return (FALSE);
943                 }
944                 client->cl_rawcred.client_principal =
945                         mem_alloc(sizeof(*client->cl_rawcred.client_principal)
946                             + export_name.length);
947                 client->cl_rawcred.client_principal->len = export_name.length;
948                 memcpy(client->cl_rawcred.client_principal->name,
949                     export_name.value, export_name.length);
950                 gss_release_buffer(&min_stat, &export_name);
951                 client->cl_rawcred.svc_principal =
952                         client->cl_sname->sn_principal;
953                 client->cl_rawcred.service = gc->gc_svc;
954
955                 /*
956                  * Use gss_pname_to_uid to map to unix creds. For
957                  * kerberos5, this uses krb5_aname_to_localname.
958                  */
959                 svc_rpc_gss_build_ucred(client, client->cl_cname);
960                 svc_rpc_gss_set_flavor(client);
961                 gss_release_name(&min_stat, &client->cl_cname);
962
963 #ifdef DEBUG
964                 {
965                         gss_buffer_desc mechname;
966
967                         gss_oid_to_str(&min_stat, mech, &mechname);
968                         
969                         rpc_gss_log_debug("accepted context for %s with "
970                             "<mech %.*s, qop %d, svc %d>",
971                             client->cl_rawcred.client_principal->name,
972                             mechname.length, (char *)mechname.value,
973                             client->cl_qop, client->cl_rawcred.service);
974
975                         gss_release_buffer(&min_stat, &mechname);
976                 }
977 #endif /* DEBUG */
978         }
979         return (TRUE);
980 }
981
982 static bool_t
983 svc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
984     gss_qop_t *qop, rpc_gss_proc_t gcproc)
985 {
986         struct opaque_auth      *oa;
987         gss_buffer_desc          rpcbuf, checksum;
988         OM_uint32                maj_stat, min_stat;
989         gss_qop_t                qop_state;
990         int32_t                  rpchdr[128 / sizeof(int32_t)];
991         int32_t                 *buf;
992
993         rpc_gss_log_debug("in svc_rpc_gss_validate()");
994         
995         memset(rpchdr, 0, sizeof(rpchdr));
996
997         /* Reconstruct RPC header for signing (from xdr_callmsg). */
998         buf = rpchdr;
999         IXDR_PUT_LONG(buf, msg->rm_xid);
1000         IXDR_PUT_ENUM(buf, msg->rm_direction);
1001         IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
1002         IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
1003         IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
1004         IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
1005         oa = &msg->rm_call.cb_cred;
1006         IXDR_PUT_ENUM(buf, oa->oa_flavor);
1007         IXDR_PUT_LONG(buf, oa->oa_length);
1008         if (oa->oa_length) {
1009                 memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
1010                 buf += RNDUP(oa->oa_length) / sizeof(int32_t);
1011         }
1012         rpcbuf.value = rpchdr;
1013         rpcbuf.length = (u_char *)buf - (u_char *)rpchdr;
1014
1015         checksum.value = msg->rm_call.cb_verf.oa_base;
1016         checksum.length = msg->rm_call.cb_verf.oa_length;
1017         
1018         maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
1019                                   &qop_state);
1020         
1021         if (maj_stat != GSS_S_COMPLETE) {
1022                 rpc_gss_log_status("gss_verify_mic", client->cl_mech,
1023                     maj_stat, min_stat);
1024                 /*
1025                  * A bug in some versions of the Linux client generates a
1026                  * Destroy operation with a bogus encrypted checksum. Deleting
1027                  * the credential handle for that case causes the mount to fail.
1028                  * Since the checksum is bogus (gss_verify_mic() failed), it
1029                  * doesn't make sense to destroy the handle and not doing so
1030                  * fixes the Linux mount.
1031                  */
1032                 if (gcproc != RPCSEC_GSS_DESTROY)
1033                         client->cl_state = CLIENT_STALE;
1034                 return (FALSE);
1035         }
1036
1037         *qop = qop_state;
1038         return (TRUE);
1039 }
1040
1041 static bool_t
1042 svc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
1043     struct svc_req *rqst, u_int seq)
1044 {
1045         gss_buffer_desc         signbuf;
1046         gss_buffer_desc         mic;
1047         OM_uint32               maj_stat, min_stat;
1048         uint32_t                nseq;       
1049
1050         rpc_gss_log_debug("in svc_rpc_gss_nextverf()");
1051
1052         nseq = htonl(seq);
1053         signbuf.value = &nseq;
1054         signbuf.length = sizeof(nseq);
1055
1056         maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
1057             &signbuf, &mic);
1058
1059         if (maj_stat != GSS_S_COMPLETE) {
1060                 rpc_gss_log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
1061                 client->cl_state = CLIENT_STALE;
1062                 return (FALSE);
1063         }
1064
1065         KASSERT(mic.length <= MAX_AUTH_BYTES,
1066             ("MIC too large for RPCSEC_GSS"));
1067
1068         rqst->rq_verf.oa_flavor = RPCSEC_GSS;
1069         rqst->rq_verf.oa_length = mic.length;
1070         bcopy(mic.value, rqst->rq_verf.oa_base, mic.length);
1071
1072         gss_release_buffer(&min_stat, &mic);
1073         
1074         return (TRUE);
1075 }
1076
1077 static bool_t
1078 svc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
1079 {
1080         struct svc_rpc_gss_callback *scb;
1081         rpc_gss_lock_t  lock;
1082         void            *cookie;
1083         bool_t          cb_res;
1084         bool_t          result;
1085
1086         /*
1087          * See if we have a callback for this guy.
1088          */
1089         result = TRUE;
1090         SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
1091                 if (scb->cb_callback.program == rqst->rq_prog
1092                     && scb->cb_callback.version == rqst->rq_vers) {
1093                         /*
1094                          * This one matches. Call the callback and see
1095                          * if it wants to veto or something.
1096                          */
1097                         lock.locked = FALSE;
1098                         lock.raw_cred = &client->cl_rawcred;
1099                         cb_res = scb->cb_callback.callback(rqst,
1100                             client->cl_creds,
1101                             client->cl_ctx,
1102                             &lock,
1103                             &cookie);
1104
1105                         if (!cb_res) {
1106                                 client->cl_state = CLIENT_STALE;
1107                                 result = FALSE;
1108                                 break;
1109                         }
1110
1111                         /*
1112                          * The callback accepted the connection - it
1113                          * is responsible for freeing client->cl_creds
1114                          * now.
1115                          */
1116                         client->cl_creds = GSS_C_NO_CREDENTIAL;
1117                         client->cl_locked = lock.locked;
1118                         client->cl_cookie = cookie;
1119                         return (TRUE);
1120                 }
1121         }
1122
1123         /*
1124          * Either no callback exists for this program/version or one
1125          * of the callbacks rejected the connection. We just need to
1126          * clean up the delegated client creds, if any.
1127          */
1128         if (client->cl_creds) {
1129                 OM_uint32 min_ver;
1130                 gss_release_cred(&min_ver, &client->cl_creds);
1131         }
1132         return (result);
1133 }
1134
1135 static bool_t
1136 svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
1137 {
1138         u_int32_t offset;
1139         int word, bit;
1140         bool_t result;
1141
1142         sx_xlock(&client->cl_lock);
1143         if (seq <= client->cl_seqlast) {
1144                 /*
1145                  * The request sequence number is less than
1146                  * the largest we have seen so far. If it is
1147                  * outside the window or if we have seen a
1148                  * request with this sequence before, silently
1149                  * discard it.
1150                  */
1151                 offset = client->cl_seqlast - seq;
1152                 if (offset >= SVC_RPC_GSS_SEQWINDOW) {
1153                         result = FALSE;
1154                         goto out;
1155                 }
1156                 word = offset / 32;
1157                 bit = offset % 32;
1158                 if (client->cl_seqmask[word] & (1 << bit)) {
1159                         result = FALSE;
1160                         goto out;
1161                 }
1162         }
1163
1164         result = TRUE;
1165 out:
1166         sx_xunlock(&client->cl_lock);
1167         return (result);
1168 }
1169
1170 static void
1171 svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
1172 {
1173         int offset, i, word, bit;
1174         uint32_t carry, newcarry;
1175
1176         sx_xlock(&client->cl_lock);
1177         if (seq > client->cl_seqlast) {
1178                 /*
1179                  * This request has a sequence number greater
1180                  * than any we have seen so far. Advance the
1181                  * seq window and set bit zero of the window
1182                  * (which corresponds to the new sequence
1183                  * number)
1184                  */
1185                 offset = seq - client->cl_seqlast;
1186                 while (offset > 32) {
1187                         for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
1188                              i > 0; i--) {
1189                                 client->cl_seqmask[i] = client->cl_seqmask[i-1];
1190                         }
1191                         client->cl_seqmask[0] = 0;
1192                         offset -= 32;
1193                 }
1194                 carry = 0;
1195                 for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
1196                         newcarry = client->cl_seqmask[i] >> (32 - offset);
1197                         client->cl_seqmask[i] =
1198                                 (client->cl_seqmask[i] << offset) | carry;
1199                         carry = newcarry;
1200                 }
1201                 client->cl_seqmask[0] |= 1;
1202                 client->cl_seqlast = seq;
1203         } else {
1204                 offset = client->cl_seqlast - seq;
1205                 word = offset / 32;
1206                 bit = offset % 32;
1207                 client->cl_seqmask[word] |= (1 << bit);
1208         }
1209         sx_xunlock(&client->cl_lock);
1210 }
1211
1212 enum auth_stat
1213 svc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
1214
1215 {
1216         OM_uint32                min_stat;
1217         XDR                      xdrs;
1218         struct svc_rpc_gss_cookedcred *cc;
1219         struct svc_rpc_gss_client *client;
1220         struct rpc_gss_cred      gc;
1221         struct rpc_gss_init_res  gr;
1222         gss_qop_t                qop;
1223         int                      call_stat;
1224         enum auth_stat           result;
1225         
1226         rpc_gss_log_debug("in svc_rpc_gss()");
1227         
1228         /* Garbage collect old clients. */
1229         svc_rpc_gss_timeout_clients();
1230
1231         /* Initialize reply. */
1232         rqst->rq_verf = _null_auth;
1233
1234         /* Deserialize client credentials. */
1235         if (rqst->rq_cred.oa_length <= 0)
1236                 return (AUTH_BADCRED);
1237         
1238         memset(&gc, 0, sizeof(gc));
1239         
1240         xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
1241             rqst->rq_cred.oa_length, XDR_DECODE);
1242         
1243         if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
1244                 XDR_DESTROY(&xdrs);
1245                 return (AUTH_BADCRED);
1246         }
1247         XDR_DESTROY(&xdrs);
1248
1249         client = NULL;
1250
1251         /* Check version. */
1252         if (gc.gc_version != RPCSEC_GSS_VERSION) {
1253                 result = AUTH_BADCRED;
1254                 goto out;
1255         }
1256
1257         /* Check the proc and find the client (or create it) */
1258         if (gc.gc_proc == RPCSEC_GSS_INIT) {
1259                 if (gc.gc_handle.length != 0) {
1260                         result = AUTH_BADCRED;
1261                         goto out;
1262                 }
1263                 client = svc_rpc_gss_create_client();
1264                 refcount_acquire(&client->cl_refs);
1265         } else {
1266                 struct svc_rpc_gss_clientid *p;
1267                 if (gc.gc_handle.length != sizeof(*p)) {
1268                         result = AUTH_BADCRED;
1269                         goto out;
1270                 }
1271                 p = gc.gc_handle.value;
1272                 client = svc_rpc_gss_find_client(p);
1273                 if (!client) {
1274                         /*
1275                          * Can't find the client - we may have
1276                          * destroyed it - tell the other side to
1277                          * re-authenticate.
1278                          */
1279                         result = RPCSEC_GSS_CREDPROBLEM;
1280                         goto out;
1281                 }
1282         }
1283         cc = rqst->rq_clntcred;
1284         cc->cc_client = client;
1285         cc->cc_service = gc.gc_svc;
1286         cc->cc_seq = gc.gc_seq;
1287
1288         /*
1289          * The service and sequence number must be ignored for
1290          * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
1291          */
1292         if (gc.gc_proc != RPCSEC_GSS_INIT
1293             && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
1294                 /*
1295                  * Check for sequence number overflow.
1296                  */
1297                 if (gc.gc_seq >= MAXSEQ) {
1298                         result = RPCSEC_GSS_CTXPROBLEM;
1299                         goto out;
1300                 }
1301
1302                 /*
1303                  * Check for valid service.
1304                  */
1305                 if (gc.gc_svc != rpc_gss_svc_none &&
1306                     gc.gc_svc != rpc_gss_svc_integrity &&
1307                     gc.gc_svc != rpc_gss_svc_privacy) {
1308                         result = AUTH_BADCRED;
1309                         goto out;
1310                 }
1311         }
1312
1313         /* Handle RPCSEC_GSS control procedure. */
1314         switch (gc.gc_proc) {
1315
1316         case RPCSEC_GSS_INIT:
1317         case RPCSEC_GSS_CONTINUE_INIT:
1318                 if (rqst->rq_proc != NULLPROC) {
1319                         result = AUTH_REJECTEDCRED;
1320                         break;
1321                 }
1322
1323                 memset(&gr, 0, sizeof(gr));
1324                 if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
1325                         result = AUTH_REJECTEDCRED;
1326                         break;
1327                 }
1328
1329                 if (gr.gr_major == GSS_S_COMPLETE) {
1330                         /*
1331                          * We borrow the space for the call verf to
1332                          * pack our reply verf.
1333                          */
1334                         rqst->rq_verf = msg->rm_call.cb_verf;
1335                         if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
1336                                 result = AUTH_REJECTEDCRED;
1337                                 break;
1338                         }
1339                 } else {
1340                         rqst->rq_verf = _null_auth;
1341                 }
1342                 
1343                 call_stat = svc_sendreply(rqst,
1344                     (xdrproc_t) xdr_rpc_gss_init_res,
1345                     (caddr_t) &gr);
1346
1347                 gss_release_buffer(&min_stat, &gr.gr_token);
1348
1349                 if (!call_stat) {
1350                         result = AUTH_FAILED;
1351                         break;
1352                 }
1353
1354                 if (gr.gr_major == GSS_S_COMPLETE)
1355                         client->cl_state = CLIENT_ESTABLISHED;
1356
1357                 result = RPCSEC_GSS_NODISPATCH;
1358                 break;
1359                 
1360         case RPCSEC_GSS_DATA:
1361         case RPCSEC_GSS_DESTROY:
1362                 if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
1363                         result = RPCSEC_GSS_NODISPATCH;
1364                         break;
1365                 }
1366
1367                 if (!svc_rpc_gss_validate(client, msg, &qop, gc.gc_proc)) {
1368                         result = RPCSEC_GSS_CREDPROBLEM;
1369                         break;
1370                 }
1371                 
1372                 /*
1373                  * We borrow the space for the call verf to pack our
1374                  * reply verf.
1375                  */
1376                 rqst->rq_verf = msg->rm_call.cb_verf;
1377                 if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
1378                         result = RPCSEC_GSS_CTXPROBLEM;
1379                         break;
1380                 }
1381
1382                 svc_rpc_gss_update_seq(client, gc.gc_seq);
1383
1384                 /*
1385                  * Change the SVCAUTH ops on the request to point at
1386                  * our own code so that we can unwrap the arguments
1387                  * and wrap the result. The caller will re-set this on
1388                  * every request to point to a set of null wrap/unwrap
1389                  * methods. Acquire an extra reference to the client
1390                  * which will be released by svc_rpc_gss_release()
1391                  * after the request has finished processing.
1392                  */
1393                 refcount_acquire(&client->cl_refs);
1394                 rqst->rq_auth.svc_ah_ops = &svc_auth_gss_ops;
1395                 rqst->rq_auth.svc_ah_private = cc;
1396
1397                 if (gc.gc_proc == RPCSEC_GSS_DATA) {
1398                         /*
1399                          * We might be ready to do a callback to the server to
1400                          * see if it wants to accept/reject the connection.
1401                          */
1402                         sx_xlock(&client->cl_lock);
1403                         if (!client->cl_done_callback) {
1404                                 client->cl_done_callback = TRUE;
1405                                 client->cl_qop = qop;
1406                                 client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1407                                         client->cl_rawcred.mechanism, qop);
1408                                 if (!svc_rpc_gss_callback(client, rqst)) {
1409                                         result = AUTH_REJECTEDCRED;
1410                                         sx_xunlock(&client->cl_lock);
1411                                         break;
1412                                 }
1413                         }
1414                         sx_xunlock(&client->cl_lock);
1415
1416                         /*
1417                          * If the server has locked this client to a
1418                          * particular service+qop pair, enforce that
1419                          * restriction now.
1420                          */
1421                         if (client->cl_locked) {
1422                                 if (client->cl_rawcred.service != gc.gc_svc) {
1423                                         result = AUTH_FAILED;
1424                                         break;
1425                                 } else if (client->cl_qop != qop) {
1426                                         result = AUTH_BADVERF;
1427                                         break;
1428                                 }
1429                         }
1430
1431                         /*
1432                          * If the qop changed, look up the new qop
1433                          * name for rawcred.
1434                          */
1435                         if (client->cl_qop != qop) {
1436                                 client->cl_qop = qop;
1437                                 client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1438                                         client->cl_rawcred.mechanism, qop);
1439                         }
1440
1441                         /*
1442                          * Make sure we use the right service value
1443                          * for unwrap/wrap.
1444                          */
1445                         if (client->cl_rawcred.service != gc.gc_svc) {
1446                                 client->cl_rawcred.service = gc.gc_svc;
1447                                 svc_rpc_gss_set_flavor(client);
1448                         }
1449
1450                         result = AUTH_OK;
1451                 } else {
1452                         if (rqst->rq_proc != NULLPROC) {
1453                                 result = AUTH_REJECTEDCRED;
1454                                 break;
1455                         }
1456
1457                         call_stat = svc_sendreply(rqst,
1458                             (xdrproc_t) xdr_void, (caddr_t) NULL);
1459
1460                         if (!call_stat) {
1461                                 result = AUTH_FAILED;
1462                                 break;
1463                         }
1464
1465                         svc_rpc_gss_forget_client(client);
1466
1467                         result = RPCSEC_GSS_NODISPATCH;
1468                         break;
1469                 }
1470                 break;
1471
1472         default:
1473                 result = AUTH_BADCRED;
1474                 break;
1475         }
1476 out:
1477         if (client)
1478                 svc_rpc_gss_release_client(client);
1479
1480         xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
1481         return (result);
1482 }
1483
1484 static bool_t
1485 svc_rpc_gss_wrap(SVCAUTH *auth, struct mbuf **mp)
1486 {
1487         struct svc_rpc_gss_cookedcred *cc;
1488         struct svc_rpc_gss_client *client;
1489         
1490         rpc_gss_log_debug("in svc_rpc_gss_wrap()");
1491
1492         cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1493         client = cc->cc_client;
1494         if (client->cl_state != CLIENT_ESTABLISHED
1495             || cc->cc_service == rpc_gss_svc_none || *mp == NULL) {
1496                 return (TRUE);
1497         }
1498         
1499         return (xdr_rpc_gss_wrap_data(mp,
1500                 client->cl_ctx, client->cl_qop,
1501                 cc->cc_service, cc->cc_seq));
1502 }
1503
1504 static bool_t
1505 svc_rpc_gss_unwrap(SVCAUTH *auth, struct mbuf **mp)
1506 {
1507         struct svc_rpc_gss_cookedcred *cc;
1508         struct svc_rpc_gss_client *client;
1509
1510         rpc_gss_log_debug("in svc_rpc_gss_unwrap()");
1511         
1512         cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1513         client = cc->cc_client;
1514         if (client->cl_state != CLIENT_ESTABLISHED
1515             || cc->cc_service == rpc_gss_svc_none) {
1516                 return (TRUE);
1517         }
1518
1519         return (xdr_rpc_gss_unwrap_data(mp,
1520                 client->cl_ctx, client->cl_qop,
1521                 cc->cc_service, cc->cc_seq));
1522 }
1523
1524 static void
1525 svc_rpc_gss_release(SVCAUTH *auth)
1526 {
1527         struct svc_rpc_gss_cookedcred *cc;
1528         struct svc_rpc_gss_client *client;
1529
1530         rpc_gss_log_debug("in svc_rpc_gss_release()");
1531
1532         cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1533         client = cc->cc_client;
1534         svc_rpc_gss_release_client(client);
1535 }