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