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