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