0d4f78d9668396aaebb05ab97804cfb1e8d5ed5a
[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         } else {
373                 printerr(1, "WARNING: unknown/unsupport mech OID\n");
374         }
375
376         res = 0;
377 out_rel_buf:
378         gss_release_buffer(&min_stat, &name);
379 out_err:
380         return res;
381 }
382
383 void
384 handle_nullreq(FILE *f) {
385         /* XXX initialize to a random integer to reduce chances of unnecessary
386          * invalidation of existing ctx's on restarting svcgssd. */
387         static u_int32_t        handle_seq = 0;
388         char                    in_tok_buf[TOKEN_BUF_SIZE];
389         char                    in_handle_buf[15];
390         char                    out_handle_buf[15];
391         gss_buffer_desc         in_tok = {.value = in_tok_buf},
392                                 out_tok = {.value = NULL},
393                                 in_handle = {.value = in_handle_buf},
394                                 out_handle = {.value = out_handle_buf},
395                                 ctx_token = {.value = NULL},
396                                 ignore_out_tok = {.value = NULL},
397         /* XXX isn't there a define for this?: */
398                                 null_token = {.value = NULL};
399         u_int32_t               ret_flags;
400         gss_ctx_id_t            ctx = GSS_C_NO_CONTEXT;
401         gss_name_t              client_name = NULL;
402         gss_OID                 mech = GSS_C_NO_OID;
403         u_int32_t               maj_stat = GSS_S_FAILURE, min_stat = 0;
404         u_int32_t               ignore_min_stat;
405         struct svc_cred         cred;
406         static char             *lbuf = NULL;
407         static int              lbuflen = 0;
408         static char             *cp;
409         int32_t                 ctx_endtime;
410         char                    *hostbased_name = NULL;
411
412         printerr(1, "handling null request\n");
413
414         if (readline(fileno(f), &lbuf, &lbuflen) != 1) {
415                 printerr(0, "WARNING: handle_nullreq: "
416                             "failed reading request\n");
417                 return;
418         }
419
420         cp = lbuf;
421
422         in_handle.length = (size_t) qword_get(&cp, in_handle.value,
423                                               sizeof(in_handle_buf));
424 #ifdef DEBUG
425         print_hexl("in_handle", in_handle.value, in_handle.length);
426 #endif
427
428         in_tok.length = (size_t) qword_get(&cp, in_tok.value,
429                                            sizeof(in_tok_buf));
430 #ifdef DEBUG
431         print_hexl("in_tok", in_tok.value, in_tok.length);
432 #endif
433
434         if (in_handle.length != 0) { /* CONTINUE_INIT case */
435                 if (in_handle.length != sizeof(ctx)) {
436                         printerr(0, "WARNING: handle_nullreq: "
437                                     "input handle has unexpected length %d\n",
438                                     in_handle.length);
439                         goto out_err;
440                 }
441                 /* in_handle is the context id stored in the out_handle
442                  * for the GSS_S_CONTINUE_NEEDED case below.  */
443                 memcpy(&ctx, in_handle.value, in_handle.length);
444         }
445
446         if (svcgssd_limit_krb5_enctypes()) {
447                 goto out_err;
448         }
449
450         maj_stat = gss_accept_sec_context(&min_stat, &ctx, gssd_creds,
451                         &in_tok, GSS_C_NO_CHANNEL_BINDINGS, &client_name,
452                         &mech, &out_tok, &ret_flags, NULL, NULL);
453
454         if (maj_stat == GSS_S_CONTINUE_NEEDED) {
455                 printerr(1, "gss_accept_sec_context GSS_S_CONTINUE_NEEDED\n");
456
457                 /* Save the context handle for future calls */
458                 out_handle.length = sizeof(ctx);
459                 memcpy(out_handle.value, &ctx, sizeof(ctx));
460                 goto continue_needed;
461         }
462         else if (maj_stat != GSS_S_COMPLETE) {
463                 printerr(1, "WARNING: gss_accept_sec_context failed\n");
464                 pgsserr("handle_nullreq: gss_accept_sec_context",
465                         maj_stat, min_stat, mech);
466                 goto out_err;
467         }
468         if (get_ids(client_name, mech, &cred)) {
469                 /* get_ids() prints error msg */
470                 maj_stat = GSS_S_BAD_NAME; /* XXX ? */
471                 goto out_err;
472         }
473         if (get_hostbased_client_name(client_name, mech, &hostbased_name)) {
474                 /* get_hostbased_client_name() prints error msg */
475                 maj_stat = GSS_S_BAD_NAME; /* XXX ? */
476                 goto out_err;
477         }
478
479         /* Context complete. Pass handle_seq in out_handle to use
480          * for context lookup in the kernel. */
481         handle_seq++;
482         out_handle.length = sizeof(handle_seq);
483         memcpy(out_handle.value, &handle_seq, sizeof(handle_seq));
484
485         /* kernel needs ctx to calculate verifier on null response, so
486          * must give it context before doing null call: */
487         if (serialize_context_for_kernel(ctx, &ctx_token, mech, &ctx_endtime)) {
488                 printerr(0, "WARNING: handle_nullreq: "
489                             "serialize_context_for_kernel failed\n");
490                 maj_stat = GSS_S_FAILURE;
491                 goto out_err;
492         }
493         /* We no longer need the gss context */
494         gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok);
495
496         do_svc_downcall(&out_handle, &cred, mech, &ctx_token, ctx_endtime,
497                         hostbased_name);
498 continue_needed:
499         send_response(&in_handle, &in_tok, maj_stat, min_stat,
500                         &out_handle, &out_tok);
501 out:
502         if (ctx_token.value != NULL)
503                 free(ctx_token.value);
504         if (out_tok.value != NULL)
505                 gss_release_buffer(&ignore_min_stat, &out_tok);
506         if (client_name)
507                 gss_release_name(&ignore_min_stat, &client_name);
508         free(hostbased_name);
509         printerr(1, "finished handling null request\n");
510         return;
511
512 out_err:
513         if (ctx != GSS_C_NO_CONTEXT)
514                 gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok);
515         send_response(&in_handle, &in_tok, maj_stat, min_stat,
516                         &null_token, &null_token);
517         goto out;
518 }