nfs-ganesha 1.4

svcgssd_proc.c

Go to the documentation of this file.
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 }