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 #include "svcgssd_krb5.h"
63 extern char * mech2file(gss_OID mech);
64 #define SVCGSSD_CONTEXT_CHANNEL "/proc/net/rpc/auth.rpcsec.context/channel"
65 #define SVCGSSD_INIT_CHANNEL "/proc/net/rpc/auth.rpcsec.init/channel"
67 #define TOKEN_BUF_SIZE 8192
73 gid_t cr_groups[NGROUPS];
75 static char vbuf[RPC_CHAN_BUF_SIZE];
78 do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred,
79 gss_OID mech, gss_buffer_desc *context_token,
80 int32_t endtime, char *client_name)
87 printerr(1, "doing downcall\n");
88 if ((fname = mech2file(mech)) == NULL)
90 f = fopen(SVCGSSD_CONTEXT_CHANNEL, "w");
92 printerr(0, "WARNING: unable to open downcall channel "
94 SVCGSSD_CONTEXT_CHANNEL, strerror(errno));
97 setvbuf(f, vbuf, _IOLBF, RPC_CHAN_BUF_SIZE);
98 qword_printhex(f, out_handle->value, out_handle->length);
99 /* XXX are types OK for the rest of this? */
100 /* For context cache, use the actual context endtime */
101 qword_printint(f, endtime);
102 qword_printint(f, cred->cr_uid);
103 qword_printint(f, cred->cr_gid);
104 qword_printint(f, cred->cr_ngroups);
105 printerr(2, "mech: %s, hndl len: %d, ctx len %d, timeout: %d (%d from now), "
106 "clnt: %s, uid: %d, gid: %d, num aux grps: %d:\n",
107 fname, out_handle->length, context_token->length,
108 endtime, endtime - time(0),
109 client_name ? client_name : "<null>",
110 cred->cr_uid, cred->cr_gid, cred->cr_ngroups);
111 for (i=0; i < cred->cr_ngroups; i++) {
112 qword_printint(f, cred->cr_groups[i]);
113 printerr(2, " (%4d) %d\n", i+1, cred->cr_groups[i]);
115 qword_print(f, fname);
116 qword_printhex(f, context_token->value, context_token->length);
118 qword_print(f, client_name);
121 printerr(1, "WARNING: error writing to downcall channel "
122 "%s: %s\n", SVCGSSD_CONTEXT_CHANNEL, strerror(errno));
127 printerr(1, "WARNING: downcall failed\n");
131 struct gss_verifier {
133 gss_buffer_desc body;
136 #define RPCSEC_GSS_SEQ_WIN 5
139 send_response(gss_buffer_desc *in_handle, gss_buffer_desc *in_token,
140 u_int32_t maj_stat, u_int32_t min_stat,
141 gss_buffer_desc *out_handle, gss_buffer_desc *out_token)
143 char buf[2 * TOKEN_BUF_SIZE];
145 int blen = sizeof(buf);
149 printerr(1, "sending null reply\n");
151 qword_addhex(&bp, &blen, in_handle->value, in_handle->length);
152 qword_addhex(&bp, &blen, in_token->value, in_token->length);
153 /* For init cache, only needed for a short time */
154 qword_addint(&bp, &blen, time(0) + 60);
155 qword_adduint(&bp, &blen, maj_stat);
156 qword_adduint(&bp, &blen, min_stat);
157 qword_addhex(&bp, &blen, out_handle->value, out_handle->length);
158 qword_addhex(&bp, &blen, out_token->value, out_token->length);
159 qword_addeol(&bp, &blen);
161 printerr(0, "WARNING: send_respsonse: message too long\n");
164 g = open(SVCGSSD_INIT_CHANNEL, O_WRONLY);
166 printerr(0, "WARNING: open %s failed: %s\n",
167 SVCGSSD_INIT_CHANNEL, strerror(errno));
171 printerr(3, "writing message: %s", buf);
172 if (write(g, buf, bp - buf) == -1) {
173 printerr(0, "WARNING: failed to write message\n");
181 #define rpc_auth_ok 0
182 #define rpc_autherr_badcred 1
183 #define rpc_autherr_rejectedcred 2
184 #define rpc_autherr_badverf 3
185 #define rpc_autherr_rejectedverf 4
186 #define rpc_autherr_tooweak 5
187 #define rpcsec_gsserr_credproblem 13
188 #define rpcsec_gsserr_ctxproblem 14
191 add_supplementary_groups(char *secname, char *name, struct svc_cred *cred)
194 static gid_t *groups = NULL;
196 cred->cr_ngroups = NGROUPS;
197 ret = nfs4_gss_princ_to_grouplist(secname, name,
198 cred->cr_groups, &cred->cr_ngroups);
200 groups = realloc(groups, cred->cr_ngroups*sizeof(gid_t));
201 ret = nfs4_gss_princ_to_grouplist(secname, name,
202 groups, &cred->cr_ngroups);
204 cred->cr_ngroups = 0;
206 if (cred->cr_ngroups > NGROUPS)
207 cred->cr_ngroups = NGROUPS;
208 memcpy(cred->cr_groups, groups,
209 cred->cr_ngroups*sizeof(gid_t));
215 get_ids(gss_name_t client_name, gss_OID mech, struct svc_cred *cred)
217 u_int32_t maj_stat, min_stat;
218 gss_buffer_desc name;
222 gss_OID name_type = GSS_C_NO_OID;
225 maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type);
226 if (maj_stat != GSS_S_COMPLETE) {
227 pgsserr("get_ids: gss_display_name",
228 maj_stat, min_stat, mech);
231 if (name.length >= 0xffff || /* be certain name.length+1 doesn't overflow */
232 !(sname = calloc(name.length + 1, 1))) {
233 printerr(0, "WARNING: get_ids: error allocating %d bytes "
234 "for sname\n", name.length + 1);
235 gss_release_buffer(&min_stat, &name);
238 memcpy(sname, name.value, name.length);
239 printerr(1, "sname = %s\n", sname);
240 gss_release_buffer(&min_stat, &name);
243 if ((secname = mech2file(mech)) == NULL) {
244 printerr(0, "WARNING: get_ids: error mapping mech to "
245 "file for name '%s'\n", sname);
249 res = nfs4_gss_princ_to_ids(secname, sname, &uid, &gid);
252 * -ENOENT means there was no mapping, any other error
253 * value means there was an error trying to do the
255 * If there was no mapping, we send down the value -1
256 * to indicate that the anonuid/anongid for the export
259 if (res == -ENOENT) {
262 cred->cr_ngroups = 0;
266 printerr(1, "WARNING: get_ids: failed to map name '%s' "
267 "to uid/gid: %s\n", sname, strerror(-res));
272 add_supplementary_groups(secname, sname, cred);
282 print_hexl(const char *description, unsigned char *cp, int length)
287 printf("%s (length %d)\n", description, length);
289 for (i = 0; i < length; i += 0x10) {
290 printf(" %04x: ", (u_int)i);
292 jm = jm > 16 ? 16 : jm;
294 for (j = 0; j < jm; j++) {
296 printf("%02x ", (u_int)cp[i+j]);
298 printf("%02x", (u_int)cp[i+j]);
300 for (; j < 16; j++) {
308 for (j = 0; j < jm; j++) {
310 c = isprint(c) ? c : '.';
319 get_krb5_hostbased_name (gss_buffer_desc *name, char **hostbased_name)
321 char *p, *sname = NULL;
322 if (strchr(name->value, '@') && strchr(name->value, '/')) {
323 if ((sname = calloc(name->length, 1)) == NULL) {
324 printerr(0, "ERROR: get_krb5_hostbased_name failed "
325 "to allocate %d bytes\n", name->length);
328 /* read in name and instance and replace '/' with '@' */
329 sscanf(name->value, "%[^@]", sname);
330 p = strrchr(sname, '/');
331 if (p == NULL) { /* The '@' preceeded the '/' */
337 *hostbased_name = sname;
342 get_hostbased_client_name(gss_name_t client_name, gss_OID mech,
343 char **hostbased_name)
345 u_int32_t maj_stat, min_stat;
346 gss_buffer_desc name;
347 gss_OID name_type = GSS_C_NO_OID;
351 *hostbased_name = NULL; /* preset in case we fail */
353 /* Get the client's gss authenticated name */
354 maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type);
355 if (maj_stat != GSS_S_COMPLETE) {
356 pgsserr("get_hostbased_client_name: gss_display_name",
357 maj_stat, min_stat, mech);
360 if (name.length >= 0xffff) { /* don't overflow */
361 printerr(0, "ERROR: get_hostbased_client_name: "
362 "received gss_name is too long (%d bytes)\n",
367 /* For Kerberos, transform the NT_KRB5_PRINCIPAL name to
368 * an NT_HOSTBASED_SERVICE name */
369 if (g_OID_equal(&krb5oid, mech)) {
370 if (get_krb5_hostbased_name(&name, &cname) == 0)
371 *hostbased_name = cname;
374 /* No support for SPKM3, just print a warning (for now) */
375 if (g_OID_equal(&spkm3oid, mech)) {
376 printerr(1, "WARNING: get_hostbased_client_name: "
377 "no hostbased_name support for SPKM3\n");
382 gss_release_buffer(&min_stat, &name);
388 handle_nullreq(FILE *f) {
389 /* XXX initialize to a random integer to reduce chances of unnecessary
390 * invalidation of existing ctx's on restarting svcgssd. */
391 static u_int32_t handle_seq = 0;
392 char in_tok_buf[TOKEN_BUF_SIZE];
393 char in_handle_buf[15];
394 char out_handle_buf[15];
395 gss_buffer_desc in_tok = {.value = in_tok_buf},
396 out_tok = {.value = NULL},
397 in_handle = {.value = in_handle_buf},
398 out_handle = {.value = out_handle_buf},
399 ctx_token = {.value = NULL},
400 ignore_out_tok = {.value = NULL},
401 /* XXX isn't there a define for this?: */
402 null_token = {.value = NULL};
404 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
405 gss_name_t client_name = NULL;
406 gss_OID mech = GSS_C_NO_OID;
407 u_int32_t maj_stat = GSS_S_FAILURE, min_stat = 0;
408 u_int32_t ignore_min_stat;
409 struct svc_cred cred;
410 static char *lbuf = NULL;
411 static int lbuflen = 0;
414 char *hostbased_name = NULL;
416 printerr(1, "handling null request\n");
418 if (readline(fileno(f), &lbuf, &lbuflen) != 1) {
419 printerr(0, "WARNING: handle_nullreq: "
420 "failed reading request\n");
426 in_handle.length = (size_t) qword_get(&cp, in_handle.value,
427 sizeof(in_handle_buf));
429 print_hexl("in_handle", in_handle.value, in_handle.length);
432 in_tok.length = (size_t) qword_get(&cp, in_tok.value,
435 print_hexl("in_tok", in_tok.value, in_tok.length);
438 if (in_handle.length != 0) { /* CONTINUE_INIT case */
439 if (in_handle.length != sizeof(ctx)) {
440 printerr(0, "WARNING: handle_nullreq: "
441 "input handle has unexpected length %d\n",
445 /* in_handle is the context id stored in the out_handle
446 * for the GSS_S_CONTINUE_NEEDED case below. */
447 memcpy(&ctx, in_handle.value, in_handle.length);
450 if (svcgssd_limit_krb5_enctypes()) {
454 maj_stat = gss_accept_sec_context(&min_stat, &ctx, gssd_creds,
455 &in_tok, GSS_C_NO_CHANNEL_BINDINGS, &client_name,
456 &mech, &out_tok, &ret_flags, NULL, NULL);
458 if (maj_stat == GSS_S_CONTINUE_NEEDED) {
459 printerr(1, "gss_accept_sec_context GSS_S_CONTINUE_NEEDED\n");
461 /* Save the context handle for future calls */
462 out_handle.length = sizeof(ctx);
463 memcpy(out_handle.value, &ctx, sizeof(ctx));
464 goto continue_needed;
466 else if (maj_stat != GSS_S_COMPLETE) {
467 printerr(1, "WARNING: gss_accept_sec_context failed\n");
468 pgsserr("handle_nullreq: gss_accept_sec_context",
469 maj_stat, min_stat, mech);
472 if (get_ids(client_name, mech, &cred)) {
473 /* get_ids() prints error msg */
474 maj_stat = GSS_S_BAD_NAME; /* XXX ? */
477 if (get_hostbased_client_name(client_name, mech, &hostbased_name)) {
478 /* get_hostbased_client_name() prints error msg */
479 maj_stat = GSS_S_BAD_NAME; /* XXX ? */
483 /* Context complete. Pass handle_seq in out_handle to use
484 * for context lookup in the kernel. */
486 out_handle.length = sizeof(handle_seq);
487 memcpy(out_handle.value, &handle_seq, sizeof(handle_seq));
489 /* kernel needs ctx to calculate verifier on null response, so
490 * must give it context before doing null call: */
491 if (serialize_context_for_kernel(ctx, &ctx_token, mech, &ctx_endtime)) {
492 printerr(0, "WARNING: handle_nullreq: "
493 "serialize_context_for_kernel failed\n");
494 maj_stat = GSS_S_FAILURE;
497 /* We no longer need the gss context */
498 gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok);
500 do_svc_downcall(&out_handle, &cred, mech, &ctx_token, ctx_endtime,
503 send_response(&in_handle, &in_tok, maj_stat, min_stat,
504 &out_handle, &out_tok);
506 if (ctx_token.value != NULL)
507 free(ctx_token.value);
508 if (out_tok.value != NULL)
509 gss_release_buffer(&ignore_min_stat, &out_tok);
511 gss_release_name(&ignore_min_stat, &client_name);
512 free(hostbased_name);
513 printerr(1, "finished handling null request\n");
517 if (ctx != GSS_C_NO_CONTEXT)
518 gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok);
519 send_response(&in_handle, &in_tok, maj_stat, min_stat,
520 &null_token, &null_token);