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