4 Copyright (c) 2000 The Regents of the University of Michigan.
7 Copyright (c) 2002 Bruce Fields <bfields@UMICH.EDU>
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions
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.
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.
36 #include <sys/param.h>
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"
59 #define TOKEN_BUF_SIZE 8192
65 gid_t cr_groups[NGROUPS];
69 do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred,
70 gss_OID mech, gss_buffer_desc *context_token)
77 printerr(1, "doing downcall\n");
78 if ((fname = mech2file(mech)) == NULL)
80 f = fopen(SVCGSSD_CONTEXT_CHANNEL, "w");
82 printerr(0, "WARNING: unable to open downcall channel "
84 SVCGSSD_CONTEXT_CHANNEL, strerror(errno));
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);
101 printerr(0, "WARNING: downcall failed\n");
105 struct gss_verifier {
107 gss_buffer_desc body;
110 #define RPCSEC_GSS_SEQ_WIN 5
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)
117 char buf[2 * TOKEN_BUF_SIZE];
119 int blen = sizeof(buf);
123 printerr(1, "sending null reply\n");
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);
134 printerr(0, "WARNING: send_respsonse: message too long\n");
137 g = open(SVCGSSD_INIT_CHANNEL, O_WRONLY);
139 printerr(0, "WARNING: open %s failed: %s\n",
140 SVCGSSD_INIT_CHANNEL, strerror(errno));
144 printerr(3, "writing message: %s", buf);
145 if (write(g, buf, bp - buf) == -1) {
146 printerr(0, "WARNING: failed to write message\n");
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
164 add_supplementary_groups(char *secname, char *name, struct svc_cred *cred)
167 static gid_t *groups = NULL;
169 cred->cr_ngroups = NGROUPS;
170 ret = nfs4_gss_princ_to_grouplist(secname, name,
171 cred->cr_groups, &cred->cr_ngroups);
173 groups = realloc(groups, cred->cr_ngroups*sizeof(gid_t));
174 ret = nfs4_gss_princ_to_grouplist(secname, name,
175 groups, &cred->cr_ngroups);
177 cred->cr_ngroups = 0;
179 if (cred->cr_ngroups > NGROUPS)
180 cred->cr_ngroups = NGROUPS;
181 memcpy(cred->cr_groups, groups,
182 cred->cr_ngroups*sizeof(gid_t));
188 get_ids(gss_name_t client_name, gss_OID mech, struct svc_cred *cred)
190 u_int32_t maj_stat, min_stat;
191 gss_buffer_desc name;
195 gss_OID name_type = GSS_C_NO_OID;
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);
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);
211 memcpy(sname, name.value, name.length);
212 printerr(1, "sname = %s\n", sname);
213 gss_release_buffer(&min_stat, &name);
216 if ((secname = mech2file(mech)) == NULL) {
217 printerr(0, "WARNING: get_ids: error mapping mech to "
218 "file for name '%s'\n", sname);
221 nfs4_init_name_mapping(NULL); /* XXX: should only do this once */
222 res = nfs4_gss_princ_to_ids(secname, sname, &uid, &gid);
225 * -ENOENT means there was no mapping, any other error
226 * value means there was an error trying to do the
228 * If there was no mapping, we send down the value -1
229 * to indicate that the anonuid/anongid for the export
232 if (res == -ENOENT) {
235 cred->cr_ngroups = 0;
239 printerr(0, "WARNING: get_ids: failed to map name '%s' "
240 "to uid/gid: %s\n", sname, strerror(-res));
245 add_supplementary_groups(secname, sname, cred);
254 print_hexl(int pri, unsigned char *cp, int length)
259 printerr(pri, "length %d\n",length);
262 for (i = 0; i < length; i += 0x10) {
263 printerr(pri, " %04x: ", (u_int)i);
265 jm = jm > 16 ? 16 : jm;
267 for (j = 0; j < jm; j++) {
269 printerr(pri,"%02x ", (u_int)cp[i+j]);
271 printerr(pri,"%02x", (u_int)cp[i+j]);
273 for (; j < 16; j++) {
281 for (j = 0; j < jm; j++) {
283 c = isprint(c) ? c : '.';
284 printerr(pri,"%c", c);
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};
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;
317 printerr(1, "handling null request\n");
319 if (readline(fileno(f), &lbuf, &lbuflen) != 1) {
320 printerr(0, "WARNING: handle_nullreq: "
321 "failed reading request\n");
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);
332 in_tok.length = (size_t) qword_get(&cp, in_tok.value,
334 printerr(2, "in_tok: \n");
335 print_hexl(2, in_tok.value, in_tok.length);
337 if (in_tok.length < 0) {
338 printerr(0, "WARNING: handle_nullreq: "
339 "failed parsing request\n");
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",
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);
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);
359 if (maj_stat == GSS_S_CONTINUE_NEEDED) {
360 printerr(1, "gss_accept_sec_context GSS_S_CONTINUE_NEEDED\n");
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;
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);
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);
379 gss_release_name(&ignore_min_stat, &client_name);
382 /* Context complete. Pass handle_seq in out_handle to use
383 * for context lookup in the kernel. */
385 out_handle.length = sizeof(handle_seq);
386 memcpy(out_handle.value, &handle_seq, sizeof(handle_seq));
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;
396 /* We no longer need the gss context */
397 gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok);
399 do_svc_downcall(&out_handle, &cred, mech, &ctx_token);
401 send_response(f, &in_handle, &in_tok, maj_stat, min_stat,
402 &out_handle, &out_tok);
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");
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);