]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/gssd/svcgssd_proc.c
nfs-utils: Add support to svcgssd to limit the negotiated enctypes
[nfs-utils.git] / utils / gssd / svcgssd_proc.c
1 /*
2   svc_in_gssd_proc.c
3
4   Copyright (c) 2000 The Regents of the University of Michigan.
5   All rights reserved.
6
7   Copyright (c) 2002 Bruce Fields <bfields@UMICH.EDU>
8
9   Redistribution and use in source and binary forms, with or without
10   modification, are permitted provided that the following conditions
11   are met:
12
13   1. Redistributions of source code must retain the above copyright
14      notice, this list of conditions and the following disclaimer.
15   2. Redistributions in binary form must reproduce the above copyright
16      notice, this list of conditions and the following disclaimer in the
17      documentation and/or other materials provided with the distribution.
18   3. Neither the name of the University nor the names of its
19      contributors may be used to endorse or promote products derived
20      from this software without specific prior written permission.
21
22   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
23   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
24   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25   DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
29   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
34 */
35
36 #ifdef HAVE_CONFIG_H
37 #include <config.h>
38 #endif  /* HAVE_CONFIG_H */
39
40 #include <sys/param.h>
41 #include <sys/stat.h>
42 #include <rpc/rpc.h>
43
44 #include <pwd.h>
45 #include <stdio.h>
46 #include <unistd.h>
47 #include <ctype.h>
48 #include <string.h>
49 #include <fcntl.h>
50 #include <errno.h>
51 #include <nfsidmap.h>
52 #include <nfslib.h>
53 #include <time.h>
54
55 #include "svcgssd.h"
56 #include "gss_util.h"
57 #include "err_util.h"
58 #include "context.h"
59 #include "gss_oids.h"
60 #include "svcgssd_krb5.h"
61
62 extern char * mech2file(gss_OID mech);
63 #define SVCGSSD_CONTEXT_CHANNEL "/proc/net/rpc/auth.rpcsec.context/channel"
64 #define SVCGSSD_INIT_CHANNEL    "/proc/net/rpc/auth.rpcsec.init/channel"
65
66 #define TOKEN_BUF_SIZE          8192
67
68 struct svc_cred {
69         uid_t   cr_uid;
70         gid_t   cr_gid;
71         int     cr_ngroups;
72         gid_t   cr_groups[NGROUPS];
73 };
74
75 static int
76 do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred,
77                 gss_OID mech, gss_buffer_desc *context_token,
78                 int32_t endtime, char *client_name)
79 {
80         FILE *f;
81         int i;
82         char *fname = NULL;
83         int err;
84
85         printerr(1, "doing downcall\n");
86         if ((fname = mech2file(mech)) == NULL)
87                 goto out_err;
88         f = fopen(SVCGSSD_CONTEXT_CHANNEL, "w");
89         if (f == NULL) {
90                 printerr(0, "WARNING: unable to open downcall channel "
91                              "%s: %s\n",
92                              SVCGSSD_CONTEXT_CHANNEL, strerror(errno));
93                 goto out_err;
94         }
95         qword_printhex(f, out_handle->value, out_handle->length);
96         /* XXX are types OK for the rest of this? */
97         /* For context cache, use the actual context endtime */
98         qword_printint(f, endtime);
99         qword_printint(f, cred->cr_uid);
100         qword_printint(f, cred->cr_gid);
101         qword_printint(f, cred->cr_ngroups);
102         printerr(2, "mech: %s, hndl len: %d, ctx len %d, timeout: %d (%d from now), "
103                  "clnt: %s, uid: %d, gid: %d, num aux grps: %d:\n",
104                  fname, out_handle->length, context_token->length,
105                  endtime, endtime - time(0),
106                  client_name ? client_name : "<null>",
107                  cred->cr_uid, cred->cr_gid, cred->cr_ngroups);
108         for (i=0; i < cred->cr_ngroups; i++) {
109                 qword_printint(f, cred->cr_groups[i]);
110                 printerr(2, "  (%4d) %d\n", i+1, cred->cr_groups[i]);
111         }
112         qword_print(f, fname);
113         qword_printhex(f, context_token->value, context_token->length);
114         if (client_name)
115                 qword_print(f, client_name);
116         err = qword_eol(f);
117         if (err) {
118                 printerr(1, "WARNING: error writing to downcall channel "
119                          "%s: %s\n", SVCGSSD_CONTEXT_CHANNEL, strerror(errno));
120         }
121         fclose(f);
122         return err;
123 out_err:
124         printerr(1, "WARNING: downcall failed\n");
125         return -1;
126 }
127
128 struct gss_verifier {
129         u_int32_t       flav;
130         gss_buffer_desc body;
131 };
132
133 #define RPCSEC_GSS_SEQ_WIN      5
134
135 static int
136 send_response(gss_buffer_desc *in_handle, gss_buffer_desc *in_token,
137               u_int32_t maj_stat, u_int32_t min_stat,
138               gss_buffer_desc *out_handle, gss_buffer_desc *out_token)
139 {
140         char buf[2 * TOKEN_BUF_SIZE];
141         char *bp = buf;
142         int blen = sizeof(buf);
143         /* XXXARG: */
144         int g;
145
146         printerr(1, "sending null reply\n");
147
148         qword_addhex(&bp, &blen, in_handle->value, in_handle->length);
149         qword_addhex(&bp, &blen, in_token->value, in_token->length);
150         /* For init cache, only needed for a short time */
151         qword_addint(&bp, &blen, time(0) + 60);
152         qword_adduint(&bp, &blen, maj_stat);
153         qword_adduint(&bp, &blen, min_stat);
154         qword_addhex(&bp, &blen, out_handle->value, out_handle->length);
155         qword_addhex(&bp, &blen, out_token->value, out_token->length);
156         qword_addeol(&bp, &blen);
157         if (blen <= 0) {
158                 printerr(0, "WARNING: send_respsonse: message too long\n");
159                 return -1;
160         }
161         g = open(SVCGSSD_INIT_CHANNEL, O_WRONLY);
162         if (g == -1) {
163                 printerr(0, "WARNING: open %s failed: %s\n",
164                                 SVCGSSD_INIT_CHANNEL, strerror(errno));
165                 return -1;
166         }
167         *bp = '\0';
168         printerr(3, "writing message: %s", buf);
169         if (write(g, buf, bp - buf) == -1) {
170                 printerr(0, "WARNING: failed to write message\n");
171                 close(g);
172                 return -1;
173         }
174         close(g);
175         return 0;
176 }
177
178 #define rpc_auth_ok                     0
179 #define rpc_autherr_badcred             1
180 #define rpc_autherr_rejectedcred        2
181 #define rpc_autherr_badverf             3
182 #define rpc_autherr_rejectedverf        4
183 #define rpc_autherr_tooweak             5
184 #define rpcsec_gsserr_credproblem       13
185 #define rpcsec_gsserr_ctxproblem        14
186
187 static void
188 add_supplementary_groups(char *secname, char *name, struct svc_cred *cred)
189 {
190         int ret;
191         static gid_t *groups = NULL;
192
193         cred->cr_ngroups = NGROUPS;
194         ret = nfs4_gss_princ_to_grouplist(secname, name,
195                         cred->cr_groups, &cred->cr_ngroups);
196         if (ret < 0) {
197                 groups = realloc(groups, cred->cr_ngroups*sizeof(gid_t));
198                 ret = nfs4_gss_princ_to_grouplist(secname, name,
199                                 groups, &cred->cr_ngroups);
200                 if (ret < 0)
201                         cred->cr_ngroups = 0;
202                 else {
203                         if (cred->cr_ngroups > NGROUPS)
204                                 cred->cr_ngroups = NGROUPS;
205                         memcpy(cred->cr_groups, groups,
206                                         cred->cr_ngroups*sizeof(gid_t));
207                 }
208         }
209 }
210
211 static int
212 get_ids(gss_name_t client_name, gss_OID mech, struct svc_cred *cred)
213 {
214         u_int32_t       maj_stat, min_stat;
215         gss_buffer_desc name;
216         char            *sname;
217         int             res = -1;
218         uid_t           uid, gid;
219         gss_OID         name_type = GSS_C_NO_OID;
220         char            *secname;
221
222         maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type);
223         if (maj_stat != GSS_S_COMPLETE) {
224                 pgsserr("get_ids: gss_display_name",
225                         maj_stat, min_stat, mech);
226                 goto out;
227         }
228         if (name.length >= 0xffff || /* be certain name.length+1 doesn't overflow */
229             !(sname = calloc(name.length + 1, 1))) {
230                 printerr(0, "WARNING: get_ids: error allocating %d bytes "
231                         "for sname\n", name.length + 1);
232                 gss_release_buffer(&min_stat, &name);
233                 goto out;
234         }
235         memcpy(sname, name.value, name.length);
236         printerr(1, "sname = %s\n", sname);
237         gss_release_buffer(&min_stat, &name);
238
239         res = -EINVAL;
240         if ((secname = mech2file(mech)) == NULL) {
241                 printerr(0, "WARNING: get_ids: error mapping mech to "
242                         "file for name '%s'\n", sname);
243                 goto out_free;
244         }
245
246         res = nfs4_gss_princ_to_ids(secname, sname, &uid, &gid);
247         if (res < 0) {
248                 /*
249                  * -ENOENT means there was no mapping, any other error
250                  * value means there was an error trying to do the
251                  * mapping.
252                  * If there was no mapping, we send down the value -1
253                  * to indicate that the anonuid/anongid for the export
254                  * should be used.
255                  */
256                 if (res == -ENOENT) {
257                         cred->cr_uid = -1;
258                         cred->cr_gid = -1;
259                         cred->cr_ngroups = 0;
260                         res = 0;
261                         goto out_free;
262                 }
263                 printerr(1, "WARNING: get_ids: failed to map name '%s' "
264                         "to uid/gid: %s\n", sname, strerror(-res));
265                 goto out_free;
266         }
267         cred->cr_uid = uid;
268         cred->cr_gid = gid;
269         add_supplementary_groups(secname, sname, cred);
270         res = 0;
271 out_free:
272         free(sname);
273 out:
274         return res;
275 }
276
277 #ifdef DEBUG
278 void
279 print_hexl(const char *description, unsigned char *cp, int length)
280 {
281         int i, j, jm;
282         unsigned char c;
283
284         printf("%s (length %d)\n", description, length);
285
286         for (i = 0; i < length; i += 0x10) {
287                 printf("  %04x: ", (u_int)i);
288                 jm = length - i;
289                 jm = jm > 16 ? 16 : jm;
290
291                 for (j = 0; j < jm; j++) {
292                         if ((j % 2) == 1)
293                                 printf("%02x ", (u_int)cp[i+j]);
294                         else
295                                 printf("%02x", (u_int)cp[i+j]);
296                 }
297                 for (; j < 16; j++) {
298                         if ((j % 2) == 1)
299                                 printf("   ");
300                         else
301                                 printf("  ");
302                 }
303                 printf(" ");
304
305                 for (j = 0; j < jm; j++) {
306                         c = cp[i+j];
307                         c = isprint(c) ? c : '.';
308                         printf("%c", c);
309                 }
310                 printf("\n");
311         }
312 }
313 #endif
314
315 static int
316 get_krb5_hostbased_name (gss_buffer_desc *name, char **hostbased_name)
317 {
318         char *p, *sname = NULL;
319         if (strchr(name->value, '@') && strchr(name->value, '/')) {
320                 if ((sname = calloc(name->length, 1)) == NULL) {
321                         printerr(0, "ERROR: get_krb5_hostbased_name failed "
322                                  "to allocate %d bytes\n", name->length);
323                         return -1;
324                 }
325                 /* read in name and instance and replace '/' with '@' */
326                 sscanf(name->value, "%[^@]", sname);
327                 p = strrchr(sname, '/');
328                 if (p == NULL) {    /* The '@' preceeded the '/' */
329                         free(sname);
330                         return -1;
331                 }
332                 *p = '@';
333         }
334         *hostbased_name = sname;
335         return 0;
336 }
337
338 static int
339 get_hostbased_client_name(gss_name_t client_name, gss_OID mech,
340                           char **hostbased_name)
341 {
342         u_int32_t       maj_stat, min_stat;
343         gss_buffer_desc name;
344         gss_OID         name_type = GSS_C_NO_OID;
345         char            *cname;
346         int             res = -1;
347
348         *hostbased_name = NULL;     /* preset in case we fail */
349
350         /* Get the client's gss authenticated name */
351         maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type);
352         if (maj_stat != GSS_S_COMPLETE) {
353                 pgsserr("get_hostbased_client_name: gss_display_name",
354                         maj_stat, min_stat, mech);
355                 goto out_err;
356         }
357         if (name.length >= 0xffff) {        /* don't overflow */
358                 printerr(0, "ERROR: get_hostbased_client_name: "
359                          "received gss_name is too long (%d bytes)\n",
360                          name.length);
361                 goto out_rel_buf;
362         }
363
364         /* For Kerberos, transform the NT_KRB5_PRINCIPAL name to
365          * an NT_HOSTBASED_SERVICE name */
366         if (g_OID_equal(&krb5oid, mech)) {
367                 if (get_krb5_hostbased_name(&name, &cname) == 0)
368                         *hostbased_name = cname;
369         }
370
371         /* No support for SPKM3, just print a warning (for now) */
372         if (g_OID_equal(&spkm3oid, mech)) {
373                 printerr(1, "WARNING: get_hostbased_client_name: "
374                          "no hostbased_name support for SPKM3\n");
375         }
376
377         res = 0;
378 out_rel_buf:
379         gss_release_buffer(&min_stat, &name);
380 out_err:
381         return res;
382 }
383
384 void
385 handle_nullreq(FILE *f) {
386         /* XXX initialize to a random integer to reduce chances of unnecessary
387          * invalidation of existing ctx's on restarting svcgssd. */
388         static u_int32_t        handle_seq = 0;
389         char                    in_tok_buf[TOKEN_BUF_SIZE];
390         char                    in_handle_buf[15];
391         char                    out_handle_buf[15];
392         gss_buffer_desc         in_tok = {.value = in_tok_buf},
393                                 out_tok = {.value = NULL},
394                                 in_handle = {.value = in_handle_buf},
395                                 out_handle = {.value = out_handle_buf},
396                                 ctx_token = {.value = NULL},
397                                 ignore_out_tok = {.value = NULL},
398         /* XXX isn't there a define for this?: */
399                                 null_token = {.value = NULL};
400         u_int32_t               ret_flags;
401         gss_ctx_id_t            ctx = GSS_C_NO_CONTEXT;
402         gss_name_t              client_name = NULL;
403         gss_OID                 mech = GSS_C_NO_OID;
404         u_int32_t               maj_stat = GSS_S_FAILURE, min_stat = 0;
405         u_int32_t               ignore_min_stat;
406         struct svc_cred         cred;
407         static char             *lbuf = NULL;
408         static int              lbuflen = 0;
409         static char             *cp;
410         int32_t                 ctx_endtime;
411         char                    *hostbased_name = NULL;
412
413         printerr(1, "handling null request\n");
414
415         if (readline(fileno(f), &lbuf, &lbuflen) != 1) {
416                 printerr(0, "WARNING: handle_nullreq: "
417                             "failed reading request\n");
418                 return;
419         }
420
421         cp = lbuf;
422
423         in_handle.length = (size_t) qword_get(&cp, in_handle.value,
424                                               sizeof(in_handle_buf));
425 #ifdef DEBUG
426         print_hexl("in_handle", in_handle.value, in_handle.length);
427 #endif
428
429         in_tok.length = (size_t) qword_get(&cp, in_tok.value,
430                                            sizeof(in_tok_buf));
431 #ifdef DEBUG
432         print_hexl("in_tok", in_tok.value, in_tok.length);
433 #endif
434
435         if (in_handle.length != 0) { /* CONTINUE_INIT case */
436                 if (in_handle.length != sizeof(ctx)) {
437                         printerr(0, "WARNING: handle_nullreq: "
438                                     "input handle has unexpected length %d\n",
439                                     in_handle.length);
440                         goto out_err;
441                 }
442                 /* in_handle is the context id stored in the out_handle
443                  * for the GSS_S_CONTINUE_NEEDED case below.  */
444                 memcpy(&ctx, in_handle.value, in_handle.length);
445         }
446
447         if (svcgssd_limit_krb5_enctypes()) {
448                 goto out_err;
449         }
450
451         maj_stat = gss_accept_sec_context(&min_stat, &ctx, gssd_creds,
452                         &in_tok, GSS_C_NO_CHANNEL_BINDINGS, &client_name,
453                         &mech, &out_tok, &ret_flags, NULL, NULL);
454
455         if (maj_stat == GSS_S_CONTINUE_NEEDED) {
456                 printerr(1, "gss_accept_sec_context GSS_S_CONTINUE_NEEDED\n");
457
458                 /* Save the context handle for future calls */
459                 out_handle.length = sizeof(ctx);
460                 memcpy(out_handle.value, &ctx, sizeof(ctx));
461                 goto continue_needed;
462         }
463         else if (maj_stat != GSS_S_COMPLETE) {
464                 printerr(1, "WARNING: gss_accept_sec_context failed\n");
465                 pgsserr("handle_nullreq: gss_accept_sec_context",
466                         maj_stat, min_stat, mech);
467                 goto out_err;
468         }
469         if (get_ids(client_name, mech, &cred)) {
470                 /* get_ids() prints error msg */
471                 maj_stat = GSS_S_BAD_NAME; /* XXX ? */
472                 goto out_err;
473         }
474         if (get_hostbased_client_name(client_name, mech, &hostbased_name)) {
475                 /* get_hostbased_client_name() prints error msg */
476                 maj_stat = GSS_S_BAD_NAME; /* XXX ? */
477                 goto out_err;
478         }
479
480         /* Context complete. Pass handle_seq in out_handle to use
481          * for context lookup in the kernel. */
482         handle_seq++;
483         out_handle.length = sizeof(handle_seq);
484         memcpy(out_handle.value, &handle_seq, sizeof(handle_seq));
485
486         /* kernel needs ctx to calculate verifier on null response, so
487          * must give it context before doing null call: */
488         if (serialize_context_for_kernel(ctx, &ctx_token, mech, &ctx_endtime)) {
489                 printerr(0, "WARNING: handle_nullreq: "
490                             "serialize_context_for_kernel failed\n");
491                 maj_stat = GSS_S_FAILURE;
492                 goto out_err;
493         }
494         /* We no longer need the gss context */
495         gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok);
496
497         do_svc_downcall(&out_handle, &cred, mech, &ctx_token, ctx_endtime,
498                         hostbased_name);
499 continue_needed:
500         send_response(&in_handle, &in_tok, maj_stat, min_stat,
501                         &out_handle, &out_tok);
502 out:
503         if (ctx_token.value != NULL)
504                 free(ctx_token.value);
505         if (out_tok.value != NULL)
506                 gss_release_buffer(&ignore_min_stat, &out_tok);
507         if (client_name)
508                 gss_release_name(&ignore_min_stat, &client_name);
509         free(hostbased_name);
510         printerr(1, "finished handling null request\n");
511         return;
512
513 out_err:
514         if (ctx != GSS_C_NO_CONTEXT)
515                 gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok);
516         send_response(&in_handle, &in_tok, maj_stat, min_stat,
517                         &null_token, &null_token);
518         goto out;
519 }