nfs-ganesha 1.4
|
00001 /* 00002 svc_in_gssd_proc.c 00003 00004 Copyright (c) 2000 The Regents of the University of Michigan. 00005 All rights reserved. 00006 00007 Copyright (c) 2002 Bruce Fields <bfields@UMICH.EDU> 00008 00009 Redistribution and use in source and binary forms, with or without 00010 modification, are permitted provided that the following conditions 00011 are met: 00012 00013 1. Redistributions of source code must retain the above copyright 00014 notice, this list of conditions and the following disclaimer. 00015 2. Redistributions in binary form must reproduce the above copyright 00016 notice, this list of conditions and the following disclaimer in the 00017 documentation and/or other materials provided with the distribution. 00018 3. Neither the name of the University nor the names of its 00019 contributors may be used to endorse or promote products derived 00020 from this software without specific prior written permission. 00021 00022 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED 00023 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 00024 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 00025 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 00026 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 00027 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 00028 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 00029 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 00030 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 00031 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 00032 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00033 00034 */ 00035 00036 #ifdef HAVE_CONFIG_H 00037 #include <config.h> 00038 #endif /* HAVE_CONFIG_H */ 00039 00040 #include <sys/param.h> 00041 #include <sys/stat.h> 00042 #include <rpc/rpc.h> 00043 00044 #include <pwd.h> 00045 #include <stdio.h> 00046 #include <unistd.h> 00047 #include <ctype.h> 00048 #include <string.h> 00049 #include <fcntl.h> 00050 #include <errno.h> 00051 #include <nfsidmap.h> 00052 #include <nfslib.h> 00053 #include <time.h> 00054 00055 #include "svcgssd.h" 00056 #include "gss_util.h" 00057 #include "err_util.h" 00058 #include "context.h" 00059 #include "misc.h" 00060 #include "gss_oids.h" 00061 #include "svcgssd_krb5.h" 00062 00063 extern char * mech2file(gss_OID mech); 00064 #define SVCGSSD_CONTEXT_CHANNEL "/proc/net/rpc/auth.rpcsec.context/channel" 00065 #define SVCGSSD_INIT_CHANNEL "/proc/net/rpc/auth.rpcsec.init/channel" 00066 00067 #define TOKEN_BUF_SIZE 8192 00068 00069 struct svc_cred { 00070 uid_t cr_uid; 00071 gid_t cr_gid; 00072 int cr_ngroups; 00073 gid_t cr_groups[NGROUPS]; 00074 }; 00075 static char vbuf[RPC_CHAN_BUF_SIZE]; 00076 00077 static int 00078 do_svc_downcall(gss_buffer_desc *out_handle, struct svc_cred *cred, 00079 gss_OID mech, gss_buffer_desc *context_token, 00080 int32_t endtime, char *client_name) 00081 { 00082 FILE *f; 00083 int i; 00084 char *fname = NULL; 00085 int err; 00086 00087 printerr(1, "doing downcall\n"); 00088 if ((fname = mech2file(mech)) == NULL) 00089 goto out_err; 00090 f = fopen(SVCGSSD_CONTEXT_CHANNEL, "w"); 00091 if (f == NULL) { 00092 printerr(0, "WARNING: unable to open downcall channel " 00093 "%s: %s\n", 00094 SVCGSSD_CONTEXT_CHANNEL, strerror(errno)); 00095 goto out_err; 00096 } 00097 setvbuf(f, vbuf, _IOLBF, RPC_CHAN_BUF_SIZE); 00098 qword_printhex(f, out_handle->value, out_handle->length); 00099 /* XXX are types OK for the rest of this? */ 00100 /* For context cache, use the actual context endtime */ 00101 qword_printint(f, endtime); 00102 qword_printint(f, cred->cr_uid); 00103 qword_printint(f, cred->cr_gid); 00104 qword_printint(f, cred->cr_ngroups); 00105 printerr(2, "mech: %s, hndl len: %d, ctx len %d, timeout: %d (%d from now), " 00106 "clnt: %s, uid: %d, gid: %d, num aux grps: %d:\n", 00107 fname, out_handle->length, context_token->length, 00108 endtime, endtime - time(0), 00109 client_name ? client_name : "<null>", 00110 cred->cr_uid, cred->cr_gid, cred->cr_ngroups); 00111 for (i=0; i < cred->cr_ngroups; i++) { 00112 qword_printint(f, cred->cr_groups[i]); 00113 printerr(2, " (%4d) %d\n", i+1, cred->cr_groups[i]); 00114 } 00115 qword_print(f, fname); 00116 qword_printhex(f, context_token->value, context_token->length); 00117 if (client_name) 00118 qword_print(f, client_name); 00119 err = qword_eol(f); 00120 if (err) { 00121 printerr(1, "WARNING: error writing to downcall channel " 00122 "%s: %s\n", SVCGSSD_CONTEXT_CHANNEL, strerror(errno)); 00123 } 00124 fclose(f); 00125 return err; 00126 out_err: 00127 printerr(1, "WARNING: downcall failed\n"); 00128 return -1; 00129 } 00130 00131 struct gss_verifier { 00132 u_int32_t flav; 00133 gss_buffer_desc body; 00134 }; 00135 00136 #define RPCSEC_GSS_SEQ_WIN 5 00137 00138 static int 00139 send_response(gss_buffer_desc *in_handle, gss_buffer_desc *in_token, 00140 u_int32_t maj_stat, u_int32_t min_stat, 00141 gss_buffer_desc *out_handle, gss_buffer_desc *out_token) 00142 { 00143 char buf[2 * TOKEN_BUF_SIZE]; 00144 char *bp = buf; 00145 int blen = sizeof(buf); 00146 /* XXXARG: */ 00147 int g; 00148 00149 printerr(1, "sending null reply\n"); 00150 00151 qword_addhex(&bp, &blen, in_handle->value, in_handle->length); 00152 qword_addhex(&bp, &blen, in_token->value, in_token->length); 00153 /* For init cache, only needed for a short time */ 00154 qword_addint(&bp, &blen, time(0) + 60); 00155 qword_adduint(&bp, &blen, maj_stat); 00156 qword_adduint(&bp, &blen, min_stat); 00157 qword_addhex(&bp, &blen, out_handle->value, out_handle->length); 00158 qword_addhex(&bp, &blen, out_token->value, out_token->length); 00159 qword_addeol(&bp, &blen); 00160 if (blen <= 0) { 00161 printerr(0, "WARNING: send_respsonse: message too long\n"); 00162 return -1; 00163 } 00164 g = open(SVCGSSD_INIT_CHANNEL, O_WRONLY); 00165 if (g == -1) { 00166 printerr(0, "WARNING: open %s failed: %s\n", 00167 SVCGSSD_INIT_CHANNEL, strerror(errno)); 00168 return -1; 00169 } 00170 *bp = '\0'; 00171 printerr(3, "writing message: %s", buf); 00172 if (write(g, buf, bp - buf) == -1) { 00173 printerr(0, "WARNING: failed to write message\n"); 00174 close(g); 00175 return -1; 00176 } 00177 close(g); 00178 return 0; 00179 } 00180 00181 #define rpc_auth_ok 0 00182 #define rpc_autherr_badcred 1 00183 #define rpc_autherr_rejectedcred 2 00184 #define rpc_autherr_badverf 3 00185 #define rpc_autherr_rejectedverf 4 00186 #define rpc_autherr_tooweak 5 00187 #define rpcsec_gsserr_credproblem 13 00188 #define rpcsec_gsserr_ctxproblem 14 00189 00190 static void 00191 add_supplementary_groups(char *secname, char *name, struct svc_cred *cred) 00192 { 00193 int ret; 00194 static gid_t *groups = NULL; 00195 00196 cred->cr_ngroups = NGROUPS; 00197 ret = nfs4_gss_princ_to_grouplist(secname, name, 00198 cred->cr_groups, &cred->cr_ngroups); 00199 if (ret < 0) { 00200 groups = realloc(groups, cred->cr_ngroups*sizeof(gid_t)); 00201 ret = nfs4_gss_princ_to_grouplist(secname, name, 00202 groups, &cred->cr_ngroups); 00203 if (ret < 0) 00204 cred->cr_ngroups = 0; 00205 else { 00206 if (cred->cr_ngroups > NGROUPS) 00207 cred->cr_ngroups = NGROUPS; 00208 memcpy(cred->cr_groups, groups, 00209 cred->cr_ngroups*sizeof(gid_t)); 00210 } 00211 } 00212 } 00213 00214 static int 00215 get_ids(gss_name_t client_name, gss_OID mech, struct svc_cred *cred) 00216 { 00217 u_int32_t maj_stat, min_stat; 00218 gss_buffer_desc name; 00219 char *sname; 00220 int res = -1; 00221 uid_t uid, gid; 00222 gss_OID name_type = GSS_C_NO_OID; 00223 char *secname; 00224 00225 maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type); 00226 if (maj_stat != GSS_S_COMPLETE) { 00227 pgsserr("get_ids: gss_display_name", 00228 maj_stat, min_stat, mech); 00229 goto out; 00230 } 00231 if (name.length >= 0xffff || /* be certain name.length+1 doesn't overflow */ 00232 !(sname = calloc(name.length + 1, 1))) { 00233 printerr(0, "WARNING: get_ids: error allocating %d bytes " 00234 "for sname\n", name.length + 1); 00235 gss_release_buffer(&min_stat, &name); 00236 goto out; 00237 } 00238 memcpy(sname, name.value, name.length); 00239 printerr(1, "sname = %s\n", sname); 00240 gss_release_buffer(&min_stat, &name); 00241 00242 res = -EINVAL; 00243 if ((secname = mech2file(mech)) == NULL) { 00244 printerr(0, "WARNING: get_ids: error mapping mech to " 00245 "file for name '%s'\n", sname); 00246 goto out_free; 00247 } 00248 00249 res = nfs4_gss_princ_to_ids(secname, sname, &uid, &gid); 00250 if (res < 0) { 00251 /* 00252 * -ENOENT means there was no mapping, any other error 00253 * value means there was an error trying to do the 00254 * mapping. 00255 * If there was no mapping, we send down the value -1 00256 * to indicate that the anonuid/anongid for the export 00257 * should be used. 00258 */ 00259 if (res == -ENOENT) { 00260 cred->cr_uid = -1; 00261 cred->cr_gid = -1; 00262 cred->cr_ngroups = 0; 00263 res = 0; 00264 goto out_free; 00265 } 00266 printerr(1, "WARNING: get_ids: failed to map name '%s' " 00267 "to uid/gid: %s\n", sname, strerror(-res)); 00268 goto out_free; 00269 } 00270 cred->cr_uid = uid; 00271 cred->cr_gid = gid; 00272 add_supplementary_groups(secname, sname, cred); 00273 res = 0; 00274 out_free: 00275 free(sname); 00276 out: 00277 return res; 00278 } 00279 00280 #ifdef DEBUG 00281 void 00282 print_hexl(const char *description, unsigned char *cp, int length) 00283 { 00284 int i, j, jm; 00285 unsigned char c; 00286 00287 printf("%s (length %d)\n", description, length); 00288 00289 for (i = 0; i < length; i += 0x10) { 00290 printf(" %04x: ", (u_int)i); 00291 jm = length - i; 00292 jm = jm > 16 ? 16 : jm; 00293 00294 for (j = 0; j < jm; j++) { 00295 if ((j % 2) == 1) 00296 printf("%02x ", (u_int)cp[i+j]); 00297 else 00298 printf("%02x", (u_int)cp[i+j]); 00299 } 00300 for (; j < 16; j++) { 00301 if ((j % 2) == 1) 00302 printf(" "); 00303 else 00304 printf(" "); 00305 } 00306 printf(" "); 00307 00308 for (j = 0; j < jm; j++) { 00309 c = cp[i+j]; 00310 c = isprint(c) ? c : '.'; 00311 printf("%c", c); 00312 } 00313 printf("\n"); 00314 } 00315 } 00316 #endif 00317 00318 static int 00319 get_krb5_hostbased_name (gss_buffer_desc *name, char **hostbased_name) 00320 { 00321 char *p, *sname = NULL; 00322 if (strchr(name->value, '@') && strchr(name->value, '/')) { 00323 if ((sname = calloc(name->length, 1)) == NULL) { 00324 printerr(0, "ERROR: get_krb5_hostbased_name failed " 00325 "to allocate %d bytes\n", name->length); 00326 return -1; 00327 } 00328 /* read in name and instance and replace '/' with '@' */ 00329 sscanf(name->value, "%[^@]", sname); 00330 p = strrchr(sname, '/'); 00331 if (p == NULL) { /* The '@' preceeded the '/' */ 00332 free(sname); 00333 return -1; 00334 } 00335 *p = '@'; 00336 } 00337 *hostbased_name = sname; 00338 return 0; 00339 } 00340 00341 static int 00342 get_hostbased_client_name(gss_name_t client_name, gss_OID mech, 00343 char **hostbased_name) 00344 { 00345 u_int32_t maj_stat, min_stat; 00346 gss_buffer_desc name; 00347 gss_OID name_type = GSS_C_NO_OID; 00348 char *cname; 00349 int res = -1; 00350 00351 *hostbased_name = NULL; /* preset in case we fail */ 00352 00353 /* Get the client's gss authenticated name */ 00354 maj_stat = gss_display_name(&min_stat, client_name, &name, &name_type); 00355 if (maj_stat != GSS_S_COMPLETE) { 00356 pgsserr("get_hostbased_client_name: gss_display_name", 00357 maj_stat, min_stat, mech); 00358 goto out_err; 00359 } 00360 if (name.length >= 0xffff) { /* don't overflow */ 00361 printerr(0, "ERROR: get_hostbased_client_name: " 00362 "received gss_name is too long (%d bytes)\n", 00363 name.length); 00364 goto out_rel_buf; 00365 } 00366 00367 /* For Kerberos, transform the NT_KRB5_PRINCIPAL name to 00368 * an NT_HOSTBASED_SERVICE name */ 00369 if (g_OID_equal(&krb5oid, mech)) { 00370 if (get_krb5_hostbased_name(&name, &cname) == 0) 00371 *hostbased_name = cname; 00372 } 00373 00374 /* No support for SPKM3, just print a warning (for now) */ 00375 if (g_OID_equal(&spkm3oid, mech)) { 00376 printerr(1, "WARNING: get_hostbased_client_name: " 00377 "no hostbased_name support for SPKM3\n"); 00378 } 00379 00380 res = 0; 00381 out_rel_buf: 00382 gss_release_buffer(&min_stat, &name); 00383 out_err: 00384 return res; 00385 } 00386 00387 void 00388 handle_nullreq(FILE *f) { 00389 /* XXX initialize to a random integer to reduce chances of unnecessary 00390 * invalidation of existing ctx's on restarting svcgssd. */ 00391 static u_int32_t handle_seq = 0; 00392 char in_tok_buf[TOKEN_BUF_SIZE]; 00393 char in_handle_buf[15]; 00394 char out_handle_buf[15]; 00395 gss_buffer_desc in_tok = {.value = in_tok_buf}, 00396 out_tok = {.value = NULL}, 00397 in_handle = {.value = in_handle_buf}, 00398 out_handle = {.value = out_handle_buf}, 00399 ctx_token = {.value = NULL}, 00400 ignore_out_tok = {.value = NULL}, 00401 /* XXX isn't there a define for this?: */ 00402 null_token = {.value = NULL}; 00403 u_int32_t ret_flags; 00404 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; 00405 gss_name_t client_name = NULL; 00406 gss_OID mech = GSS_C_NO_OID; 00407 u_int32_t maj_stat = GSS_S_FAILURE, min_stat = 0; 00408 u_int32_t ignore_min_stat; 00409 struct svc_cred cred; 00410 static char *lbuf = NULL; 00411 static int lbuflen = 0; 00412 static char *cp; 00413 int32_t ctx_endtime; 00414 char *hostbased_name = NULL; 00415 00416 printerr(1, "handling null request\n"); 00417 00418 if (readline(fileno(f), &lbuf, &lbuflen) != 1) { 00419 printerr(0, "WARNING: handle_nullreq: " 00420 "failed reading request\n"); 00421 return; 00422 } 00423 00424 cp = lbuf; 00425 00426 in_handle.length = (size_t) qword_get(&cp, in_handle.value, 00427 sizeof(in_handle_buf)); 00428 #ifdef DEBUG 00429 print_hexl("in_handle", in_handle.value, in_handle.length); 00430 #endif 00431 00432 in_tok.length = (size_t) qword_get(&cp, in_tok.value, 00433 sizeof(in_tok_buf)); 00434 #ifdef DEBUG 00435 print_hexl("in_tok", in_tok.value, in_tok.length); 00436 #endif 00437 00438 if (in_handle.length != 0) { /* CONTINUE_INIT case */ 00439 if (in_handle.length != sizeof(ctx)) { 00440 printerr(0, "WARNING: handle_nullreq: " 00441 "input handle has unexpected length %d\n", 00442 in_handle.length); 00443 goto out_err; 00444 } 00445 /* in_handle is the context id stored in the out_handle 00446 * for the GSS_S_CONTINUE_NEEDED case below. */ 00447 memcpy(&ctx, in_handle.value, in_handle.length); 00448 } 00449 00450 if (svcgssd_limit_krb5_enctypes()) { 00451 goto out_err; 00452 } 00453 00454 maj_stat = gss_accept_sec_context(&min_stat, &ctx, gssd_creds, 00455 &in_tok, GSS_C_NO_CHANNEL_BINDINGS, &client_name, 00456 &mech, &out_tok, &ret_flags, NULL, NULL); 00457 00458 if (maj_stat == GSS_S_CONTINUE_NEEDED) { 00459 printerr(1, "gss_accept_sec_context GSS_S_CONTINUE_NEEDED\n"); 00460 00461 /* Save the context handle for future calls */ 00462 out_handle.length = sizeof(ctx); 00463 memcpy(out_handle.value, &ctx, sizeof(ctx)); 00464 goto continue_needed; 00465 } 00466 else if (maj_stat != GSS_S_COMPLETE) { 00467 printerr(1, "WARNING: gss_accept_sec_context failed\n"); 00468 pgsserr("handle_nullreq: gss_accept_sec_context", 00469 maj_stat, min_stat, mech); 00470 goto out_err; 00471 } 00472 if (get_ids(client_name, mech, &cred)) { 00473 /* get_ids() prints error msg */ 00474 maj_stat = GSS_S_BAD_NAME; /* XXX ? */ 00475 goto out_err; 00476 } 00477 if (get_hostbased_client_name(client_name, mech, &hostbased_name)) { 00478 /* get_hostbased_client_name() prints error msg */ 00479 maj_stat = GSS_S_BAD_NAME; /* XXX ? */ 00480 goto out_err; 00481 } 00482 00483 /* Context complete. Pass handle_seq in out_handle to use 00484 * for context lookup in the kernel. */ 00485 handle_seq++; 00486 out_handle.length = sizeof(handle_seq); 00487 memcpy(out_handle.value, &handle_seq, sizeof(handle_seq)); 00488 00489 /* kernel needs ctx to calculate verifier on null response, so 00490 * must give it context before doing null call: */ 00491 if (serialize_context_for_kernel(ctx, &ctx_token, mech, &ctx_endtime)) { 00492 printerr(0, "WARNING: handle_nullreq: " 00493 "serialize_context_for_kernel failed\n"); 00494 maj_stat = GSS_S_FAILURE; 00495 goto out_err; 00496 } 00497 /* We no longer need the gss context */ 00498 gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok); 00499 00500 do_svc_downcall(&out_handle, &cred, mech, &ctx_token, ctx_endtime, 00501 hostbased_name); 00502 continue_needed: 00503 send_response(&in_handle, &in_tok, maj_stat, min_stat, 00504 &out_handle, &out_tok); 00505 out: 00506 if (ctx_token.value != NULL) 00507 free(ctx_token.value); 00508 if (out_tok.value != NULL) 00509 gss_release_buffer(&ignore_min_stat, &out_tok); 00510 if (client_name) 00511 gss_release_name(&ignore_min_stat, &client_name); 00512 free(hostbased_name); 00513 printerr(1, "finished handling null request\n"); 00514 return; 00515 00516 out_err: 00517 if (ctx != GSS_C_NO_CONTEXT) 00518 gss_delete_sec_context(&ignore_min_stat, &ctx, &ignore_out_tok); 00519 send_response(&in_handle, &in_tok, maj_stat, min_stat, 00520 &null_token, &null_token); 00521 goto out; 00522 }