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