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.
38 #endif /* HAVE_CONFIG_H */
40 #include <sys/param.h>
61 extern char * mech2file(gss_OID mech);
62 #define SVCGSSD_CONTEXT_CHANNEL "/proc/net/rpc/auth.rpcsec.context/channel"
63 #define SVCGSSD_INIT_CHANNEL "/proc/net/rpc/auth.rpcsec.init/channel"
65 #define TOKEN_BUF_SIZE 8192
71 gid_t cr_groups[NGROUPS];
75 do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred,
76 gss_OID mech, gss_buffer_desc *context_token,
77 int32_t endtime, char *client_name)
84 printerr(1, "doing downcall\n");
85 if ((fname = mech2file(mech)) == NULL)
87 f = fopen(SVCGSSD_CONTEXT_CHANNEL, "w");
89 printerr(0, "WARNING: unable to open downcall channel "
91 SVCGSSD_CONTEXT_CHANNEL, strerror(errno));
94 qword_printhex(f, out_handle->value, out_handle->length);
95 /* XXX are types OK for the rest of this? */
96 /* For context cache, use the actual context endtime */
97 qword_printint(f, endtime);
98 qword_printint(f, cred->cr_uid);
99 qword_printint(f, cred->cr_gid);
100 qword_printint(f, cred->cr_ngroups);
101 printerr(2, "mech: %s, hndl len: %d, ctx len %d, timeout: %d (%d from now), "
102 "clnt: %s, uid: %d, gid: %d, num aux grps: %d:\n",
103 fname, out_handle->length, context_token->length,
104 endtime, endtime - time(0),
105 client_name ? client_name : "<null>",
106 cred->cr_uid, cred->cr_gid, cred->cr_ngroups);
107 for (i=0; i < cred->cr_ngroups; i++) {
108 qword_printint(f, cred->cr_groups[i]);
109 printerr(2, " (%4d) %d\n", i+1, cred->cr_groups[i]);
111 qword_print(f, fname);
112 qword_printhex(f, context_token->value, context_token->length);
114 qword_print(f, client_name);
117 printerr(1, "WARNING: error writing to downcall channel "
118 "%s: %s\n", SVCGSSD_CONTEXT_CHANNEL, strerror(errno));
123 printerr(1, "WARNING: downcall failed\n");
127 struct gss_verifier {
129 gss_buffer_desc body;
132 #define RPCSEC_GSS_SEQ_WIN 5
135 send_response(FILE *f, gss_buffer_desc *in_handle, gss_buffer_desc *in_token,
136 u_int32_t maj_stat, u_int32_t min_stat,
137 gss_buffer_desc *out_handle, gss_buffer_desc *out_token)
139 char buf[2 * TOKEN_BUF_SIZE];
141 int blen = sizeof(buf);
145 printerr(1, "sending null reply\n");
147 qword_addhex(&bp, &blen, in_handle->value, in_handle->length);
148 qword_addhex(&bp, &blen, in_token->value, in_token->length);
149 /* For init cache, only needed for a short time */
150 qword_addint(&bp, &blen, time(0) + 60);
151 qword_adduint(&bp, &blen, maj_stat);
152 qword_adduint(&bp, &blen, min_stat);
153 qword_addhex(&bp, &blen, out_handle->value, out_handle->length);
154 qword_addhex(&bp, &blen, out_token->value, out_token->length);
155 qword_addeol(&bp, &blen);
157 printerr(0, "WARNING: send_respsonse: message too long\n");
160 g = open(SVCGSSD_INIT_CHANNEL, O_WRONLY);
162 printerr(0, "WARNING: open %s failed: %s\n",
163 SVCGSSD_INIT_CHANNEL, strerror(errno));
167 printerr(3, "writing message: %s", buf);
168 if (write(g, buf, bp - buf) == -1) {
169 printerr(0, "WARNING: failed to write message\n");
177 #define rpc_auth_ok 0
178 #define rpc_autherr_badcred 1
179 #define rpc_autherr_rejectedcred 2
180 #define rpc_autherr_badverf 3
181 #define rpc_autherr_rejectedverf 4
182 #define rpc_autherr_tooweak 5
183 #define rpcsec_gsserr_credproblem 13
184 #define rpcsec_gsserr_ctxproblem 14
187 add_supplementary_groups(char *secname, char *name, struct svc_cred *cred)
190 static gid_t *groups = NULL;
192 cred->cr_ngroups = NGROUPS;
193 ret = nfs4_gss_princ_to_grouplist(secname, name,
194 cred->cr_groups, &cred->cr_ngroups);
196 groups = realloc(groups, cred->cr_ngroups*sizeof(gid_t));
197 ret = nfs4_gss_princ_to_grouplist(secname, name,
198 groups, &cred->cr_ngroups);
200 cred->cr_ngroups = 0;
202 if (cred->cr_ngroups > NGROUPS)
203 cred->cr_ngroups = NGROUPS;
204 memcpy(cred->cr_groups, groups,
205 cred->cr_ngroups*sizeof(gid_t));
211 get_ids(gss_name_t client_name, gss_OID mech, struct svc_cred *cred)
213 u_int32_t maj_stat, min_stat;
214 gss_buffer_desc name;
218 gss_OID name_type = GSS_C_NO_OID;
221 maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type);
222 if (maj_stat != GSS_S_COMPLETE) {
223 pgsserr("get_ids: gss_display_name",
224 maj_stat, min_stat, mech);
227 if (name.length >= 0xffff || /* be certain name.length+1 doesn't overflow */
228 !(sname = calloc(name.length + 1, 1))) {
229 printerr(0, "WARNING: get_ids: error allocating %d bytes "
230 "for sname\n", name.length + 1);
231 gss_release_buffer(&min_stat, &name);
234 memcpy(sname, name.value, name.length);
235 printerr(1, "sname = %s\n", sname);
236 gss_release_buffer(&min_stat, &name);
239 if ((secname = mech2file(mech)) == NULL) {
240 printerr(0, "WARNING: get_ids: error mapping mech to "
241 "file for name '%s'\n", sname);
244 nfs4_init_name_mapping(NULL); /* XXX: should only do this once */
245 res = nfs4_gss_princ_to_ids(secname, sname, &uid, &gid);
248 * -ENOENT means there was no mapping, any other error
249 * value means there was an error trying to do the
251 * If there was no mapping, we send down the value -1
252 * to indicate that the anonuid/anongid for the export
255 if (res == -ENOENT) {
258 cred->cr_ngroups = 0;
262 printerr(1, "WARNING: get_ids: failed to map name '%s' "
263 "to uid/gid: %s\n", sname, strerror(-res));
268 add_supplementary_groups(secname, sname, cred);
278 print_hexl(const char *description, unsigned char *cp, int length)
283 printf("%s (length %d)\n", description, length);
285 for (i = 0; i < length; i += 0x10) {
286 printf(" %04x: ", (u_int)i);
288 jm = jm > 16 ? 16 : jm;
290 for (j = 0; j < jm; j++) {
292 printf("%02x ", (u_int)cp[i+j]);
294 printf("%02x", (u_int)cp[i+j]);
296 for (; j < 16; j++) {
304 for (j = 0; j < jm; j++) {
306 c = isprint(c) ? c : '.';
315 get_krb5_hostbased_name (gss_buffer_desc *name, char **hostbased_name)
317 char *p, *sname = NULL;
318 if (strchr(name->value, '@') && strchr(name->value, '/')) {
319 if ((sname = calloc(name->length, 1)) == NULL) {
320 printerr(0, "ERROR: get_krb5_hostbased_name failed "
321 "to allocate %d bytes\n", name->length);
324 /* read in name and instance and replace '/' with '@' */
325 sscanf(name->value, "%[^@]", sname);
326 p = strrchr(sname, '/');
327 if (p == NULL) { /* The '@' preceeded the '/' */
333 *hostbased_name = sname;
338 get_hostbased_client_name(gss_name_t client_name, gss_OID mech,
339 char **hostbased_name)
341 u_int32_t maj_stat, min_stat;
342 gss_buffer_desc name;
343 gss_OID name_type = GSS_C_NO_OID;
347 *hostbased_name = NULL; /* preset in case we fail */
349 /* Get the client's gss authenticated name */
350 maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type);
351 if (maj_stat != GSS_S_COMPLETE) {
352 pgsserr("get_hostbased_client_name: gss_display_name",
353 maj_stat, min_stat, mech);
356 if (name.length >= 0xffff) { /* don't overflow */
357 printerr(0, "ERROR: get_hostbased_client_name: "
358 "received gss_name is too long (%d bytes)\n",
363 /* For Kerberos, transform the NT_KRB5_PRINCIPAL name to
364 * an NT_HOSTBASED_SERVICE name */
365 if (g_OID_equal(&krb5oid, mech)) {
366 if (get_krb5_hostbased_name(&name, &cname) == 0)
367 *hostbased_name = cname;
370 /* No support for SPKM3, just print a warning (for now) */
371 if (g_OID_equal(&spkm3oid, mech)) {
372 printerr(1, "WARNING: get_hostbased_client_name: "
373 "no hostbased_name support for SPKM3\n");
378 gss_release_buffer(&min_stat, &name);
384 handle_nullreq(FILE *f) {
385 /* XXX initialize to a random integer to reduce chances of unnecessary
386 * invalidation of existing ctx's on restarting svcgssd. */
387 static u_int32_t handle_seq = 0;
388 char in_tok_buf[TOKEN_BUF_SIZE];
389 char in_handle_buf[15];
390 char out_handle_buf[15];
391 gss_buffer_desc in_tok = {.value = in_tok_buf},
392 out_tok = {.value = NULL},
393 in_handle = {.value = in_handle_buf},
394 out_handle = {.value = out_handle_buf},
395 ctx_token = {.value = NULL},
396 ignore_out_tok = {.value = NULL},
397 /* XXX isn't there a define for this?: */
398 null_token = {.value = NULL};
400 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
401 gss_name_t client_name = NULL;
402 gss_OID mech = GSS_C_NO_OID;
403 u_int32_t maj_stat = GSS_S_FAILURE, min_stat = 0;
404 u_int32_t ignore_min_stat;
405 struct svc_cred cred;
406 static char *lbuf = NULL;
407 static int lbuflen = 0;
410 char *hostbased_name = NULL;
412 printerr(1, "handling null request\n");
414 if (readline(fileno(f), &lbuf, &lbuflen) != 1) {
415 printerr(0, "WARNING: handle_nullreq: "
416 "failed reading request\n");
422 in_handle.length = (size_t) qword_get(&cp, in_handle.value,
423 sizeof(in_handle_buf));
425 print_hexl("in_handle", in_handle.value, in_handle.length);
428 in_tok.length = (size_t) qword_get(&cp, in_tok.value,
431 print_hexl("in_tok", in_tok.value, in_tok.length);
434 if (in_tok.length < 0) {
435 printerr(0, "WARNING: handle_nullreq: "
436 "failed parsing request\n");
440 if (in_handle.length != 0) { /* CONTINUE_INIT case */
441 if (in_handle.length != sizeof(ctx)) {
442 printerr(0, "WARNING: handle_nullreq: "
443 "input handle has unexpected length %d\n",
447 /* in_handle is the context id stored in the out_handle
448 * for the GSS_S_CONTINUE_NEEDED case below. */
449 memcpy(&ctx, in_handle.value, in_handle.length);
452 maj_stat = gss_accept_sec_context(&min_stat, &ctx, gssd_creds,
453 &in_tok, GSS_C_NO_CHANNEL_BINDINGS, &client_name,
454 &mech, &out_tok, &ret_flags, NULL, NULL);
456 if (maj_stat == GSS_S_CONTINUE_NEEDED) {
457 printerr(1, "gss_accept_sec_context GSS_S_CONTINUE_NEEDED\n");
459 /* Save the context handle for future calls */
460 out_handle.length = sizeof(ctx);
461 memcpy(out_handle.value, &ctx, sizeof(ctx));
462 goto continue_needed;
464 else if (maj_stat != GSS_S_COMPLETE) {
465 printerr(1, "WARNING: gss_accept_sec_context failed\n");
466 pgsserr("handle_nullreq: gss_accept_sec_context",
467 maj_stat, min_stat, mech);
470 if (get_ids(client_name, mech, &cred)) {
471 /* get_ids() prints error msg */
472 maj_stat = GSS_S_BAD_NAME; /* XXX ? */
475 if (get_hostbased_client_name(client_name, mech, &hostbased_name)) {
476 /* get_hostbased_client_name() prints error msg */
477 maj_stat = GSS_S_BAD_NAME; /* XXX ? */
481 /* Context complete. Pass handle_seq in out_handle to use
482 * for context lookup in the kernel. */
484 out_handle.length = sizeof(handle_seq);
485 memcpy(out_handle.value, &handle_seq, sizeof(handle_seq));
487 /* kernel needs ctx to calculate verifier on null response, so
488 * must give it context before doing null call: */
489 if (serialize_context_for_kernel(ctx, &ctx_token, mech, &ctx_endtime)) {
490 printerr(0, "WARNING: handle_nullreq: "
491 "serialize_context_for_kernel failed\n");
492 maj_stat = GSS_S_FAILURE;
495 /* We no longer need the gss context */
496 gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok);
498 do_svc_downcall(&out_handle, &cred, mech, &ctx_token, ctx_endtime,
501 send_response(f, &in_handle, &in_tok, maj_stat, min_stat,
502 &out_handle, &out_tok);
504 if (ctx_token.value != NULL)
505 free(ctx_token.value);
506 if (out_tok.value != NULL)
507 gss_release_buffer(&ignore_min_stat, &out_tok);
509 gss_release_name(&ignore_min_stat, &client_name);
510 free(hostbased_name);
511 printerr(1, "finished handling null request\n");
515 if (ctx != GSS_C_NO_CONTEXT)
516 gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok);
517 send_response(f, &in_handle, &in_tok, maj_stat, min_stat,
518 &null_token, &null_token);