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>
60 #include "svcgssd_krb5.h"
62 extern char * mech2file(gss_OID mech);
63 #define SVCGSSD_CONTEXT_CHANNEL "/proc/net/rpc/auth.rpcsec.context/channel"
64 #define SVCGSSD_INIT_CHANNEL "/proc/net/rpc/auth.rpcsec.init/channel"
66 #define TOKEN_BUF_SIZE 8192
72 gid_t cr_groups[NGROUPS];
76 do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred,
77 gss_OID mech, gss_buffer_desc *context_token,
78 int32_t endtime, char *client_name)
85 printerr(1, "doing downcall\n");
86 if ((fname = mech2file(mech)) == NULL)
88 f = fopen(SVCGSSD_CONTEXT_CHANNEL, "w");
90 printerr(0, "WARNING: unable to open downcall channel "
92 SVCGSSD_CONTEXT_CHANNEL, strerror(errno));
95 qword_printhex(f, out_handle->value, out_handle->length);
96 /* XXX are types OK for the rest of this? */
97 /* For context cache, use the actual context endtime */
98 qword_printint(f, endtime);
99 qword_printint(f, cred->cr_uid);
100 qword_printint(f, cred->cr_gid);
101 qword_printint(f, cred->cr_ngroups);
102 printerr(2, "mech: %s, hndl len: %d, ctx len %d, timeout: %d (%d from now), "
103 "clnt: %s, uid: %d, gid: %d, num aux grps: %d:\n",
104 fname, out_handle->length, context_token->length,
105 endtime, endtime - time(0),
106 client_name ? client_name : "<null>",
107 cred->cr_uid, cred->cr_gid, cred->cr_ngroups);
108 for (i=0; i < cred->cr_ngroups; i++) {
109 qword_printint(f, cred->cr_groups[i]);
110 printerr(2, " (%4d) %d\n", i+1, cred->cr_groups[i]);
112 qword_print(f, fname);
113 qword_printhex(f, context_token->value, context_token->length);
115 qword_print(f, client_name);
118 printerr(1, "WARNING: error writing to downcall channel "
119 "%s: %s\n", SVCGSSD_CONTEXT_CHANNEL, strerror(errno));
124 printerr(1, "WARNING: downcall failed\n");
128 struct gss_verifier {
130 gss_buffer_desc body;
133 #define RPCSEC_GSS_SEQ_WIN 5
136 send_response(gss_buffer_desc *in_handle, gss_buffer_desc *in_token,
137 u_int32_t maj_stat, u_int32_t min_stat,
138 gss_buffer_desc *out_handle, gss_buffer_desc *out_token)
140 char buf[2 * TOKEN_BUF_SIZE];
142 int blen = sizeof(buf);
146 printerr(1, "sending null reply\n");
148 qword_addhex(&bp, &blen, in_handle->value, in_handle->length);
149 qword_addhex(&bp, &blen, in_token->value, in_token->length);
150 /* For init cache, only needed for a short time */
151 qword_addint(&bp, &blen, time(0) + 60);
152 qword_adduint(&bp, &blen, maj_stat);
153 qword_adduint(&bp, &blen, min_stat);
154 qword_addhex(&bp, &blen, out_handle->value, out_handle->length);
155 qword_addhex(&bp, &blen, out_token->value, out_token->length);
156 qword_addeol(&bp, &blen);
158 printerr(0, "WARNING: send_respsonse: message too long\n");
161 g = open(SVCGSSD_INIT_CHANNEL, O_WRONLY);
163 printerr(0, "WARNING: open %s failed: %s\n",
164 SVCGSSD_INIT_CHANNEL, strerror(errno));
168 printerr(3, "writing message: %s", buf);
169 if (write(g, buf, bp - buf) == -1) {
170 printerr(0, "WARNING: failed to write message\n");
178 #define rpc_auth_ok 0
179 #define rpc_autherr_badcred 1
180 #define rpc_autherr_rejectedcred 2
181 #define rpc_autherr_badverf 3
182 #define rpc_autherr_rejectedverf 4
183 #define rpc_autherr_tooweak 5
184 #define rpcsec_gsserr_credproblem 13
185 #define rpcsec_gsserr_ctxproblem 14
188 add_supplementary_groups(char *secname, char *name, struct svc_cred *cred)
191 static gid_t *groups = NULL;
193 cred->cr_ngroups = NGROUPS;
194 ret = nfs4_gss_princ_to_grouplist(secname, name,
195 cred->cr_groups, &cred->cr_ngroups);
197 groups = realloc(groups, cred->cr_ngroups*sizeof(gid_t));
198 ret = nfs4_gss_princ_to_grouplist(secname, name,
199 groups, &cred->cr_ngroups);
201 cred->cr_ngroups = 0;
203 if (cred->cr_ngroups > NGROUPS)
204 cred->cr_ngroups = NGROUPS;
205 memcpy(cred->cr_groups, groups,
206 cred->cr_ngroups*sizeof(gid_t));
212 get_ids(gss_name_t client_name, gss_OID mech, struct svc_cred *cred)
214 u_int32_t maj_stat, min_stat;
215 gss_buffer_desc name;
219 gss_OID name_type = GSS_C_NO_OID;
222 maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type);
223 if (maj_stat != GSS_S_COMPLETE) {
224 pgsserr("get_ids: gss_display_name",
225 maj_stat, min_stat, mech);
228 if (name.length >= 0xffff || /* be certain name.length+1 doesn't overflow */
229 !(sname = calloc(name.length + 1, 1))) {
230 printerr(0, "WARNING: get_ids: error allocating %d bytes "
231 "for sname\n", name.length + 1);
232 gss_release_buffer(&min_stat, &name);
235 memcpy(sname, name.value, name.length);
236 printerr(1, "sname = %s\n", sname);
237 gss_release_buffer(&min_stat, &name);
240 if ((secname = mech2file(mech)) == NULL) {
241 printerr(0, "WARNING: get_ids: error mapping mech to "
242 "file for name '%s'\n", sname);
246 res = nfs4_gss_princ_to_ids(secname, sname, &uid, &gid);
249 * -ENOENT means there was no mapping, any other error
250 * value means there was an error trying to do the
252 * If there was no mapping, we send down the value -1
253 * to indicate that the anonuid/anongid for the export
256 if (res == -ENOENT) {
259 cred->cr_ngroups = 0;
263 printerr(1, "WARNING: get_ids: failed to map name '%s' "
264 "to uid/gid: %s\n", sname, strerror(-res));
269 add_supplementary_groups(secname, sname, cred);
279 print_hexl(const char *description, unsigned char *cp, int length)
284 printf("%s (length %d)\n", description, length);
286 for (i = 0; i < length; i += 0x10) {
287 printf(" %04x: ", (u_int)i);
289 jm = jm > 16 ? 16 : jm;
291 for (j = 0; j < jm; j++) {
293 printf("%02x ", (u_int)cp[i+j]);
295 printf("%02x", (u_int)cp[i+j]);
297 for (; j < 16; j++) {
305 for (j = 0; j < jm; j++) {
307 c = isprint(c) ? c : '.';
316 get_krb5_hostbased_name (gss_buffer_desc *name, char **hostbased_name)
318 char *p, *sname = NULL;
319 if (strchr(name->value, '@') && strchr(name->value, '/')) {
320 if ((sname = calloc(name->length, 1)) == NULL) {
321 printerr(0, "ERROR: get_krb5_hostbased_name failed "
322 "to allocate %d bytes\n", name->length);
325 /* read in name and instance and replace '/' with '@' */
326 sscanf(name->value, "%[^@]", sname);
327 p = strrchr(sname, '/');
328 if (p == NULL) { /* The '@' preceeded the '/' */
334 *hostbased_name = sname;
339 get_hostbased_client_name(gss_name_t client_name, gss_OID mech,
340 char **hostbased_name)
342 u_int32_t maj_stat, min_stat;
343 gss_buffer_desc name;
344 gss_OID name_type = GSS_C_NO_OID;
348 *hostbased_name = NULL; /* preset in case we fail */
350 /* Get the client's gss authenticated name */
351 maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type);
352 if (maj_stat != GSS_S_COMPLETE) {
353 pgsserr("get_hostbased_client_name: gss_display_name",
354 maj_stat, min_stat, mech);
357 if (name.length >= 0xffff) { /* don't overflow */
358 printerr(0, "ERROR: get_hostbased_client_name: "
359 "received gss_name is too long (%d bytes)\n",
364 /* For Kerberos, transform the NT_KRB5_PRINCIPAL name to
365 * an NT_HOSTBASED_SERVICE name */
366 if (g_OID_equal(&krb5oid, mech)) {
367 if (get_krb5_hostbased_name(&name, &cname) == 0)
368 *hostbased_name = cname;
371 /* No support for SPKM3, just print a warning (for now) */
372 if (g_OID_equal(&spkm3oid, mech)) {
373 printerr(1, "WARNING: get_hostbased_client_name: "
374 "no hostbased_name support for SPKM3\n");
379 gss_release_buffer(&min_stat, &name);
385 handle_nullreq(FILE *f) {
386 /* XXX initialize to a random integer to reduce chances of unnecessary
387 * invalidation of existing ctx's on restarting svcgssd. */
388 static u_int32_t handle_seq = 0;
389 char in_tok_buf[TOKEN_BUF_SIZE];
390 char in_handle_buf[15];
391 char out_handle_buf[15];
392 gss_buffer_desc in_tok = {.value = in_tok_buf},
393 out_tok = {.value = NULL},
394 in_handle = {.value = in_handle_buf},
395 out_handle = {.value = out_handle_buf},
396 ctx_token = {.value = NULL},
397 ignore_out_tok = {.value = NULL},
398 /* XXX isn't there a define for this?: */
399 null_token = {.value = NULL};
401 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
402 gss_name_t client_name = NULL;
403 gss_OID mech = GSS_C_NO_OID;
404 u_int32_t maj_stat = GSS_S_FAILURE, min_stat = 0;
405 u_int32_t ignore_min_stat;
406 struct svc_cred cred;
407 static char *lbuf = NULL;
408 static int lbuflen = 0;
411 char *hostbased_name = NULL;
413 printerr(1, "handling null request\n");
415 if (readline(fileno(f), &lbuf, &lbuflen) != 1) {
416 printerr(0, "WARNING: handle_nullreq: "
417 "failed reading request\n");
423 in_handle.length = (size_t) qword_get(&cp, in_handle.value,
424 sizeof(in_handle_buf));
426 print_hexl("in_handle", in_handle.value, in_handle.length);
429 in_tok.length = (size_t) qword_get(&cp, in_tok.value,
432 print_hexl("in_tok", in_tok.value, in_tok.length);
435 if (in_handle.length != 0) { /* CONTINUE_INIT case */
436 if (in_handle.length != sizeof(ctx)) {
437 printerr(0, "WARNING: handle_nullreq: "
438 "input handle has unexpected length %d\n",
442 /* in_handle is the context id stored in the out_handle
443 * for the GSS_S_CONTINUE_NEEDED case below. */
444 memcpy(&ctx, in_handle.value, in_handle.length);
447 if (svcgssd_limit_krb5_enctypes()) {
451 maj_stat = gss_accept_sec_context(&min_stat, &ctx, gssd_creds,
452 &in_tok, GSS_C_NO_CHANNEL_BINDINGS, &client_name,
453 &mech, &out_tok, &ret_flags, NULL, NULL);
455 if (maj_stat == GSS_S_CONTINUE_NEEDED) {
456 printerr(1, "gss_accept_sec_context GSS_S_CONTINUE_NEEDED\n");
458 /* Save the context handle for future calls */
459 out_handle.length = sizeof(ctx);
460 memcpy(out_handle.value, &ctx, sizeof(ctx));
461 goto continue_needed;
463 else if (maj_stat != GSS_S_COMPLETE) {
464 printerr(1, "WARNING: gss_accept_sec_context failed\n");
465 pgsserr("handle_nullreq: gss_accept_sec_context",
466 maj_stat, min_stat, mech);
469 if (get_ids(client_name, mech, &cred)) {
470 /* get_ids() prints error msg */
471 maj_stat = GSS_S_BAD_NAME; /* XXX ? */
474 if (get_hostbased_client_name(client_name, mech, &hostbased_name)) {
475 /* get_hostbased_client_name() prints error msg */
476 maj_stat = GSS_S_BAD_NAME; /* XXX ? */
480 /* Context complete. Pass handle_seq in out_handle to use
481 * for context lookup in the kernel. */
483 out_handle.length = sizeof(handle_seq);
484 memcpy(out_handle.value, &handle_seq, sizeof(handle_seq));
486 /* kernel needs ctx to calculate verifier on null response, so
487 * must give it context before doing null call: */
488 if (serialize_context_for_kernel(ctx, &ctx_token, mech, &ctx_endtime)) {
489 printerr(0, "WARNING: handle_nullreq: "
490 "serialize_context_for_kernel failed\n");
491 maj_stat = GSS_S_FAILURE;
494 /* We no longer need the gss context */
495 gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok);
497 do_svc_downcall(&out_handle, &cred, mech, &ctx_token, ctx_endtime,
500 send_response(&in_handle, &in_tok, maj_stat, min_stat,
501 &out_handle, &out_tok);
503 if (ctx_token.value != NULL)
504 free(ctx_token.value);
505 if (out_tok.value != NULL)
506 gss_release_buffer(&ignore_min_stat, &out_tok);
508 gss_release_name(&ignore_min_stat, &client_name);
509 free(hostbased_name);
510 printerr(1, "finished handling null request\n");
514 if (ctx != GSS_C_NO_CONTEXT)
515 gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok);
516 send_response(&in_handle, &in_tok, maj_stat, min_stat,
517 &null_token, &null_token);