]> git.decadent.org.uk Git - nfs-utils.git/blob - support/rpc/svc_auth_gss.c
release 1.0.7-pre2
[nfs-utils.git] / support / rpc / svc_auth_gss.c
1 /*
2   svc_auth_gss.c
3
4   Copyright (c) 2000 The Regents of the University of Michigan.
5   All rights reserved.
6
7   Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
8   All rights reserved, all wrongs reversed.
9
10   Redistribution and use in source and binary forms, with or without
11   modification, are permitted provided that the following conditions
12   are met:
13
14   1. Redistributions of source code must retain the above copyright
15      notice, this list of conditions and the following disclaimer.
16   2. Redistributions in binary form must reproduce the above copyright
17      notice, this list of conditions and the following disclaimer in the
18      documentation and/or other materials provided with the distribution.
19   3. Neither the name of the University nor the names of its
20      contributors may be used to endorse or promote products derived
21      from this software without specific prior written permission.
22
23   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
24   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
25   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26   DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
30   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
35  */
36
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <rpc/rpc.h>
41 #include <gssapi/gssapi.h>
42
43 extern SVCAUTH svc_auth_none;
44
45 /*
46  * from mit-krb5-1.2.1 mechglue/mglueP.h:
47  * Array of context IDs typed by mechanism OID
48  */
49 typedef struct gss_union_ctx_id_t {
50   gss_OID     mech_type;
51   gss_ctx_id_t    internal_ctx_id;
52 } gss_union_ctx_id_desc, *gss_union_ctx_id_t;
53
54
55
56 static bool_t   svcauth_gss_destroy();
57 static bool_t   svcauth_gss_wrap();
58 static bool_t   svcauth_gss_unwrap();
59
60 struct svc_auth_ops svc_auth_gss_ops = {
61         svcauth_gss_wrap,
62         svcauth_gss_unwrap,
63         svcauth_gss_destroy
64 };
65
66 struct svc_rpc_gss_data {
67         bool_t                  established;    /* context established */
68         gss_ctx_id_t            ctx;            /* context id */
69         struct rpc_gss_sec      sec;            /* security triple */
70         gss_buffer_desc         cname;          /* GSS client name */
71         u_int                   seq;            /* sequence number */
72         u_int                   win;            /* sequence window */
73         u_int                   seqlast;        /* last sequence number */
74         u_int32_t               seqmask;        /* bitmask of seqnums */
75         gss_name_t              client_name;    /* unparsed name string */
76 };
77
78 #define SVCAUTH_PRIVATE(auth) \
79         ((struct svc_rpc_gss_data *)(auth)->svc_ah_private)
80
81 /* Global server credentials. */
82 gss_cred_id_t           _svcauth_gss_creds;
83 static gss_name_t       _svcauth_gss_name = NULL;
84
85 bool_t
86 svcauth_gss_set_svc_name(gss_name_t name)
87 {
88         OM_uint32       maj_stat, min_stat;
89
90         log_debug("in svcauth_gss_set_svc_name()");
91
92         if (_svcauth_gss_name != NULL) {
93                 maj_stat = gss_release_name(&min_stat, &_svcauth_gss_name);
94
95                 if (maj_stat != GSS_S_COMPLETE) {
96                         log_status("gss_release_name", maj_stat, min_stat);
97                         return (FALSE);
98                 }
99                 _svcauth_gss_name = NULL;
100         }
101         maj_stat = gss_duplicate_name(&min_stat, name, &_svcauth_gss_name);
102
103         if (maj_stat != GSS_S_COMPLETE) {
104                 log_status("gss_duplicate_name", maj_stat, min_stat);
105                 return (FALSE);
106         }
107
108         return (TRUE);
109 }
110
111 static bool_t
112 svcauth_gss_import_name(char *service)
113 {
114         gss_name_t      name;
115         gss_buffer_desc namebuf;
116         OM_uint32       maj_stat, min_stat;
117
118         log_debug("in svcauth_gss_import_name()");
119
120         namebuf.value = service;
121         namebuf.length = strlen(service);
122
123         maj_stat = gss_import_name(&min_stat, &namebuf,
124                                    GSS_C_NT_HOSTBASED_SERVICE, &name);
125
126         if (maj_stat != GSS_S_COMPLETE) {
127                 log_status("gss_import_name", maj_stat, min_stat);
128                 return (FALSE);
129         }
130         if (svcauth_gss_set_svc_name(name) != TRUE) {
131                 gss_release_name(&min_stat, &name);
132                 return (FALSE);
133         }
134         return (TRUE);
135 }
136
137 static bool_t
138 svcauth_gss_acquire_cred(void)
139 {
140         OM_uint32       maj_stat, min_stat;
141
142         log_debug("in svcauth_gss_acquire_cred()");
143
144         maj_stat = gss_acquire_cred(&min_stat, _svcauth_gss_name, 0,
145                                     GSS_C_NULL_OID_SET, GSS_C_ACCEPT,
146                                     &_svcauth_gss_creds, NULL, NULL);
147
148         if (maj_stat != GSS_S_COMPLETE) {
149                 log_status("gss_acquire_cred", maj_stat, min_stat);
150                 return (FALSE);
151         }
152         return (TRUE);
153 }
154
155 static bool_t
156 svcauth_gss_release_cred(void)
157 {
158         OM_uint32       maj_stat, min_stat;
159
160         log_debug("in svcauth_gss_release_cred()");
161
162         maj_stat = gss_release_cred(&min_stat, &_svcauth_gss_creds);
163
164         if (maj_stat != GSS_S_COMPLETE) {
165                 log_status("gss_release_cred", maj_stat, min_stat);
166                 return (FALSE);
167         }
168
169         _svcauth_gss_creds = NULL;
170
171         return (TRUE);
172 }
173
174 static bool_t
175 svcauth_gss_accept_sec_context(struct svc_req *rqst,
176                                struct rpc_gss_init_res *gr)
177 {
178         struct svc_rpc_gss_data *gd;
179         struct rpc_gss_cred     *gc;
180         gss_buffer_desc          recv_tok, seqbuf, checksum;
181         gss_OID                  mech;
182         OM_uint32                maj_stat = 0, min_stat = 0, ret_flags, seq;
183
184         log_debug("in svcauth_gss_accept_context()");
185
186         gd = SVCAUTH_PRIVATE(rqst->rq_xprt->xp_auth);
187         gc = (struct rpc_gss_cred *)rqst->rq_clntcred;
188         memset(gr, 0, sizeof(*gr));
189
190         /* Deserialize arguments. */
191         memset(&recv_tok, 0, sizeof(recv_tok));
192
193         if (!svc_getargs(rqst->rq_xprt, xdr_rpc_gss_init_args,
194                          (caddr_t)&recv_tok))
195                 return (FALSE);
196
197         gr->gr_major = gss_accept_sec_context(&gr->gr_minor,
198                                               &gd->ctx,
199                                               _svcauth_gss_creds,
200                                               &recv_tok,
201                                               GSS_C_NO_CHANNEL_BINDINGS,
202                                               &gd->client_name,
203                                               &mech,
204                                               &gr->gr_token,
205                                               &ret_flags,
206                                               NULL,
207                                               NULL);
208
209         if (gr->gr_major != GSS_S_COMPLETE &&
210             gr->gr_major != GSS_S_CONTINUE_NEEDED) {
211                 log_status("accept_sec_context", gr->gr_major, gr->gr_minor);
212                 gd->ctx = GSS_C_NO_CONTEXT;
213                 gss_release_buffer(&min_stat, &gr->gr_token);
214                 return (FALSE);
215         }
216         /* ANDROS: krb5 mechglue returns ctx of size 8 - two pointers,
217          * one to the mechanism oid, one to the internal_ctx_id */
218         if ((gr->gr_ctx.value = mem_alloc(sizeof(gss_union_ctx_id_desc))) == NULL) {
219                 fprintf(stderr, "svcauth_gss_accept_context: out of memory\n");
220                 return (FALSE);
221         }
222         memcpy(gr->gr_ctx.value, gd->ctx, sizeof(gss_union_ctx_id_desc));
223         gr->gr_ctx.length = sizeof(gss_union_ctx_id_desc);
224
225         /* ANDROS: change for debugging linux kernel version...
226         gr->gr_win = sizeof(gd->seqmask) * 8;
227         */
228         gr->gr_win = 0x00000005;
229
230         /* Save client info. */
231         gd->sec.mech = mech;
232         gd->sec.qop = GSS_C_QOP_DEFAULT;
233         gd->sec.svc = gc->gc_svc;
234         gd->seq = gc->gc_seq;
235         gd->win = gr->gr_win;
236
237         if (gr->gr_major == GSS_S_COMPLETE) {
238                 maj_stat = gss_display_name(&min_stat, gd->client_name,
239                                             &gd->cname, &gd->sec.mech);
240                 if (maj_stat != GSS_S_COMPLETE) {
241                         log_status("display_name", maj_stat, min_stat);
242                         return (FALSE);
243                 }
244 #ifdef DEBUG
245 #ifdef HAVE_KRB5
246                 {
247                         gss_buffer_desc mechname;
248
249                         gss_oid_to_str(&min_stat, mech, &mechname);
250
251                         log_debug("accepted context for %.*s with "
252                                   "<mech %.*s, qop %d, svc %d>",
253                                   gd->cname.length, (char *)gd->cname.value,
254                                   mechname.length, (char *)mechname.value,
255                                   gd->sec.qop, gd->sec.svc);
256
257                         gss_release_buffer(&min_stat, &mechname);
258                 }
259 #elif HAVE_HEIMDAL
260                 log_debug("accepted context for %.*s with "
261                           "<mech {}, qop %d, svc %d>",
262                           gd->cname.length, (char *)gd->cname.value,
263                           gd->sec.qop, gd->sec.svc);
264 #endif
265 #endif /* DEBUG */
266                 seq = htonl(gr->gr_win);
267                 seqbuf.value = &seq;
268                 seqbuf.length = sizeof(seq);
269
270                 maj_stat = gss_sign(&min_stat, gd->ctx, GSS_C_QOP_DEFAULT,
271                                     &seqbuf, &checksum);
272
273                 if (maj_stat != GSS_S_COMPLETE)
274                         return (FALSE);
275
276                 rqst->rq_xprt->xp_verf.oa_flavor = RPCSEC_GSS;
277                 rqst->rq_xprt->xp_verf.oa_base = checksum.value;
278                 rqst->rq_xprt->xp_verf.oa_length = checksum.length;
279         }
280         return (TRUE);
281 }
282
283 static bool_t
284 svcauth_gss_validate(struct svc_rpc_gss_data *gd, struct rpc_msg *msg)
285 {
286         struct opaque_auth      *oa;
287         gss_buffer_desc          rpcbuf, checksum;
288         OM_uint32                maj_stat, min_stat, qop_state;
289         u_char                   rpchdr[128];
290         int32_t                 *buf;
291
292         log_debug("in svcauth_gss_validate()");
293
294         memset(rpchdr, 0, sizeof(rpchdr));
295
296         /* XXX - Reconstruct RPC header for signing (from xdr_callmsg). */
297         buf = (int32_t *)rpchdr;
298         IXDR_PUT_LONG(buf, msg->rm_xid);
299         IXDR_PUT_ENUM(buf, msg->rm_direction);
300         IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
301         IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
302         IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
303         IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
304         oa = &msg->rm_call.cb_cred;
305         IXDR_PUT_ENUM(buf, oa->oa_flavor);
306         IXDR_PUT_LONG(buf, oa->oa_length);
307         if (oa->oa_length) {
308                 memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
309                 buf += RNDUP(oa->oa_length) / sizeof(int32_t);
310         }
311         rpcbuf.value = rpchdr;
312         rpcbuf.length = (u_char *)buf - rpchdr;
313
314         checksum.value = msg->rm_call.cb_verf.oa_base;
315         checksum.length = msg->rm_call.cb_verf.oa_length;
316
317         maj_stat = gss_verify_mic(&min_stat, gd->ctx, &rpcbuf, &checksum,
318                                   &qop_state);
319
320         if (maj_stat != GSS_S_COMPLETE) {
321                 log_status("gss_verify_mic", maj_stat, min_stat);
322                 return (FALSE);
323         }
324         return (TRUE);
325 }
326
327 bool_t
328 svcauth_gss_nextverf(struct svc_req *rqst, u_int num)
329 {
330         struct svc_rpc_gss_data *gd;
331         gss_buffer_desc          signbuf, checksum;
332         OM_uint32                maj_stat, min_stat;
333
334         log_debug("in svcauth_gss_nextverf()");
335
336         if (rqst->rq_xprt->xp_auth == NULL)
337                 return (FALSE);
338
339         gd = SVCAUTH_PRIVATE(rqst->rq_xprt->xp_auth);
340
341         signbuf.value = &num;
342         signbuf.length = sizeof(num);
343
344         maj_stat = gss_get_mic(&min_stat, gd->ctx, gd->sec.qop,
345                                &signbuf, &checksum);
346
347         if (maj_stat != GSS_S_COMPLETE) {
348                 log_status("gss_get_mic", maj_stat, min_stat);
349                 return (FALSE);
350         }
351         rqst->rq_xprt->xp_verf.oa_flavor = RPCSEC_GSS;
352         rqst->rq_xprt->xp_verf.oa_base = (caddr_t)checksum.value;
353         rqst->rq_xprt->xp_verf.oa_length = (u_int)checksum.length;
354
355         return (TRUE);
356 }
357
358 enum auth_stat
359 _svcauth_gss(struct svc_req *rqst, struct rpc_msg *msg, bool_t *no_dispatch)
360 {
361         XDR                      xdrs;
362         SVCAUTH                 *auth;
363         struct svc_rpc_gss_data *gd;
364         struct rpc_gss_cred     *gc;
365         struct rpc_gss_init_res  gr;
366         int                      call_stat, offset;
367
368         log_debug("in svcauth_gss()");
369
370         /* Initialize reply. */
371         rqst->rq_xprt->xp_verf = _null_auth;
372
373         /* Allocate and set up server auth handle. */
374         if (rqst->rq_xprt->xp_auth == NULL ||
375             rqst->rq_xprt->xp_auth == &svc_auth_none) {
376                 if ((auth = calloc(sizeof(*auth), 1)) == NULL) {
377                         fprintf(stderr, "svcauth_gss: out_of_memory\n");
378                         return (AUTH_FAILED);
379                 }
380                 if ((gd = calloc(sizeof(*gd), 1)) == NULL) {
381                         fprintf(stderr, "svcauth_gss: out_of_memory\n");
382                         return (AUTH_FAILED);
383                 }
384                 auth->svc_ah_ops = &svc_auth_gss_ops;
385                 SVCAUTH_PRIVATE(auth) = gd;
386                 rqst->rq_xprt->xp_auth = auth;
387         }
388         else gd = SVCAUTH_PRIVATE(rqst->rq_xprt->xp_auth);
389
390         /* Deserialize client credentials. */
391         if (rqst->rq_cred.oa_length <= 0)
392                 return (AUTH_BADCRED);
393
394         gc = (struct rpc_gss_cred *)rqst->rq_clntcred;
395         memset(gc, 0, sizeof(*gc));
396
397         xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
398                       rqst->rq_cred.oa_length, XDR_DECODE);
399
400         if (!xdr_rpc_gss_cred(&xdrs, gc)) {
401                 XDR_DESTROY(&xdrs);
402                 return (AUTH_BADCRED);
403         }
404         XDR_DESTROY(&xdrs);
405
406         /* Check version. */
407         if (gc->gc_v != RPCSEC_GSS_VERSION)
408                 return (AUTH_BADCRED);
409
410         /* Check RPCSEC_GSS service. */
411         if (gc->gc_svc != RPCSEC_GSS_SVC_NONE &&
412             gc->gc_svc != RPCSEC_GSS_SVC_INTEGRITY &&
413             gc->gc_svc != RPCSEC_GSS_SVC_PRIVACY)
414                 return (AUTH_BADCRED);
415
416         /* Check sequence number. */
417         if (gd->established) {
418                 if (gc->gc_seq > MAXSEQ)
419                         return (RPCSEC_GSS_CTXPROBLEM);
420
421                 if ((offset = gd->seqlast - gc->gc_seq) < 0) {
422                         gd->seqlast = gc->gc_seq;
423                         offset = 0 - offset;
424                         gd->seqmask <<= offset;
425                         offset = 0;
426                 }
427                 else if (offset >= gd->win || (gd->seqmask & (1 << offset))) {
428                         *no_dispatch = 1;
429                         return (RPCSEC_GSS_CTXPROBLEM);
430                 }
431                 gd->seq = gc->gc_seq;
432                 gd->seqmask |= (1 << offset);
433         }
434
435         if (gd->established) {
436                 rqst->rq_clntname = (char *)gd->client_name;
437                 rqst->rq_svcname = (char *)gd->ctx;
438         }
439
440         /* Handle RPCSEC_GSS control procedure. */
441         switch (gc->gc_proc) {
442
443         case RPCSEC_GSS_INIT:
444         case RPCSEC_GSS_CONTINUE_INIT:
445                 if (rqst->rq_proc != NULLPROC)
446                         return (AUTH_FAILED);           /* XXX ? */
447
448                 if (_svcauth_gss_name == NULL) {
449                         if (!svcauth_gss_import_name("nfs"))
450                                 return (AUTH_FAILED);
451                 }
452
453                 if (!svcauth_gss_acquire_cred())
454                         return (AUTH_FAILED);
455
456                 if (!svcauth_gss_accept_sec_context(rqst, &gr))
457                         return (AUTH_REJECTEDCRED);
458
459                 if (!svcauth_gss_nextverf(rqst, htonl(gr.gr_win)))
460                         return (AUTH_FAILED);
461
462                 *no_dispatch = TRUE;
463
464                 call_stat = svc_sendreply(rqst->rq_xprt, xdr_rpc_gss_init_res,
465                                           (caddr_t)&gr);
466
467                 if (!call_stat)
468                         return (AUTH_FAILED);
469
470                 if (gr.gr_major == GSS_S_COMPLETE)
471                         gd->established = TRUE;
472
473                 break;
474
475         case RPCSEC_GSS_DATA:
476                 if (!svcauth_gss_validate(gd, msg))
477                         return (RPCSEC_GSS_CREDPROBLEM);
478
479                 if (!svcauth_gss_nextverf(rqst, htonl(gc->gc_seq)))
480                         return (AUTH_FAILED);
481                 break;
482
483         case RPCSEC_GSS_DESTROY:
484                 if (rqst->rq_proc != NULLPROC)
485                         return (AUTH_FAILED);           /* XXX ? */
486
487                 if (!svcauth_gss_validate(gd, msg))
488                         return (RPCSEC_GSS_CREDPROBLEM);
489
490                 if (!svcauth_gss_nextverf(rqst, htonl(gc->gc_seq)))
491                         return (AUTH_FAILED);
492
493                 if (!svcauth_gss_release_cred())
494                         return (AUTH_FAILED);
495
496                 SVCAUTH_DESTROY(rqst->rq_xprt->xp_auth);
497                 rqst->rq_xprt->xp_auth = &svc_auth_none;
498
499                 break;
500
501         default:
502                 return (AUTH_REJECTEDCRED);
503                 break;
504         }
505         return (AUTH_OK);
506 }
507
508 bool_t
509 svcauth_gss_destroy(SVCAUTH *auth)
510 {
511         struct svc_rpc_gss_data *gd;
512         OM_uint32                min_stat;
513
514         log_debug("in svcauth_gss_destroy()");
515
516         gd = SVCAUTH_PRIVATE(auth);
517
518         gss_delete_sec_context(&min_stat, &gd->ctx, GSS_C_NO_BUFFER);
519         gss_release_buffer(&min_stat, &gd->cname);
520
521         if (gd->client_name)
522                 gss_release_name(&min_stat, &gd->client_name);
523
524         mem_free(gd, sizeof(*gd));
525         mem_free(auth, sizeof(*auth));
526
527         return (TRUE);
528 }
529
530 bool_t
531 svcauth_gss_wrap(SVCAUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
532 {
533         struct svc_rpc_gss_data *gd;
534
535         log_debug("in svcauth_gss_wrap()");
536
537         gd = SVCAUTH_PRIVATE(auth);
538
539         if (!gd->established || gd->sec.svc == RPCSEC_GSS_SVC_NONE) {
540                 return ((*xdr_func)(xdrs, xdr_ptr));
541         }
542         return (xdr_rpc_gss_data(xdrs, xdr_func, xdr_ptr,
543                                  gd->ctx, gd->sec.qop,
544                                  gd->sec.svc, gd->seq));
545 }
546
547 bool_t
548 svcauth_gss_unwrap(SVCAUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
549 {
550         struct svc_rpc_gss_data *gd;
551
552         log_debug("in svcauth_gss_unwrap()");
553
554         gd = SVCAUTH_PRIVATE(auth);
555
556         if (!gd->established || gd->sec.svc == RPCSEC_GSS_SVC_NONE) {
557                 return ((*xdr_func)(xdrs, xdr_ptr));
558         }
559         return (xdr_rpc_gss_data(xdrs, xdr_func, xdr_ptr,
560                                  gd->ctx, gd->sec.qop,
561                                  gd->sec.svc, gd->seq));
562 }
563
564 char *
565 svcauth_gss_get_principal(SVCAUTH *auth)
566 {
567         struct svc_rpc_gss_data *gd;
568         char *pname;
569
570         gd = SVCAUTH_PRIVATE(auth);
571
572         if (gd->cname.length == 0)
573                 return (NULL);
574
575         if ((pname = malloc(gd->cname.length + 1)) == NULL)
576                 return (NULL);
577
578         memcpy(pname, gd->cname.value, gd->cname.length);
579         pname[gd->cname.length] = '\0';
580
581         return (pname);
582 }