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