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