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