root/usr.sbin/gssd/gssd.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2008 Isilon Inc http://www.isilon.com/
 * Authors: Doug Rabson <dfr@rabson.org>
 * Developed with Red Inc: Alfred Perlstein <alfred@freebsd.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/linker.h>
#include <sys/module.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <ctype.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <krb5.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <gssapi/gssapi.h>
#ifdef MK_MITKRB5
#include <gssapi/gssapi_krb5.h>
#endif
#include <rpc/rpc.h>
#include <rpc/rpc_com.h>

#include "gssd.h"

#ifndef _PATH_GSS_MECH
#define _PATH_GSS_MECH  "/etc/gss/mech"
#endif
#define GSSD_CREDENTIAL_CACHE_FILE      "/tmp/krb5cc_gssd"

struct gss_resource {
        LIST_ENTRY(gss_resource) gr_link;
        uint64_t        gr_id;  /* identifier exported to kernel */
        void*           gr_res; /* GSS-API resource pointer */
};
LIST_HEAD(gss_resource_list, gss_resource) gss_resources;
int gss_resource_count;
uint32_t gss_next_id;
uint32_t gss_start_time;
int debug_level;
static char ccfile_dirlist[PATH_MAX + 1], ccfile_substring[NAME_MAX + 1];
static char pref_realm[1024];
static int verbose;
static int hostbased_initiator_cred;
/* 1.2.752.43.13.14 */
static gss_OID_desc gss_krb5_set_allowable_enctypes_x_desc =
{6, (void *) "\x2a\x85\x70\x2b\x0d\x0e"};
static gss_OID GSS_KRB5_SET_ALLOWABLE_ENCTYPES_X =
    &gss_krb5_set_allowable_enctypes_x_desc;
static gss_OID_desc gss_krb5_mech_oid_x_desc =
{9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
static gss_OID GSS_KRB5_MECH_OID_X =
    &gss_krb5_mech_oid_x_desc;

static void gssd_load_mech(void);
static int find_ccache_file(const char *, uid_t, char *);
static int is_a_valid_tgt_cache(const char *, uid_t, int *, time_t *);
static void gssd_verbose_out(const char *, ...);
static krb5_error_code gssd_get_cc_from_keytab(const char *);
static OM_uint32 gssd_get_user_cred(OM_uint32 *, uid_t, gss_cred_id_t *);
void gssd_terminate(int);

extern void gssd_1(struct svc_req *rqstp, SVCXPRT *transp);

int
main(int argc, char **argv)
{
        /*
         * We provide an RPC service on a Netlink socket. The kernel's GSS API
         * code will multicast its calls, we will listen to them, receive them,
         * process them and reply.
         */
        int oldmask, ch, debug, jailed;
        SVCXPRT *xprt;
        size_t jailed_size;

        /*
         * Initialize the credential cache file name substring and the
         * search directory list.
         */
        strlcpy(ccfile_substring, "krb5cc_", sizeof(ccfile_substring));
        ccfile_dirlist[0] = '\0';
        pref_realm[0] = '\0';
        debug = 0;
        verbose = 0;
        while ((ch = getopt(argc, argv, "dhvs:c:r:")) != -1) {
                switch (ch) {
                case 'd':
                        debug_level++;
                        break;
                case 'h':
                        /*
                         * Enable use of a host based initiator credential
                         * in the default keytab file.
                         */
                        hostbased_initiator_cred = 1;
                        break;
                case 'v':
                        verbose = 1;
                        break;
                case 's':
                        /*
                         * Set the directory search list. This enables use of
                         * find_ccache_file() to search the directories for a
                         * suitable credentials cache file.
                         */
                        strlcpy(ccfile_dirlist, optarg, sizeof(ccfile_dirlist));
                        break;
                case 'c':
                        /*
                         * Specify a non-default credential cache file
                         * substring.
                         */
                        strlcpy(ccfile_substring, optarg,
                            sizeof(ccfile_substring));
                        break;
                case 'r':
                        /*
                         * Set the preferred realm for the credential cache tgt.
                         */
                        strlcpy(pref_realm, optarg, sizeof(pref_realm));
                        break;
                default:
                        fprintf(stderr,
                            "usage: %s [-d] [-s dir-list] [-c file-substring]"
                            " [-r preferred-realm]\n", argv[0]);
                        exit(1);
                        break;
                }
        }

        gssd_load_mech();

        if (!debug_level) {
                if (daemon(0, 0) != 0)
                        err(1, "Can't daemonize");
                signal(SIGINT, SIG_IGN);
                signal(SIGQUIT, SIG_IGN);
                signal(SIGHUP, SIG_IGN);
        }
        signal(SIGTERM, gssd_terminate);
        signal(SIGPIPE, gssd_terminate);

        if ((xprt = svc_nl_create("kgss")) == NULL) {
                if (debug_level == 0) {
                        syslog(LOG_ERR,
                            "Can't create transport for local gssd socket");
                        exit(1);
                }
                err(1, "Can't create transport for local gssd socket");
        }
        if (!svc_reg(xprt, GSSD, GSSDVERS, gssd_1, NULL)) {
                if (debug_level == 0) {
                        syslog(LOG_ERR,
                            "Can't register service for local gssd socket");
                        exit(1);
                }
                err(1, "Can't register service for local gssd socket");
        }

        LIST_INIT(&gss_resources);
        gss_next_id = 1;
        gss_start_time = time(0);
        svc_run();

        return (0);
}

static void
gssd_load_mech(void)
{
        FILE            *fp;
        char            buf[256];
        char            *p;
        char            *name, *oid, *lib, *kobj;

        fp = fopen(_PATH_GSS_MECH, "r");
        if (!fp)
                return;

        while (fgets(buf, sizeof(buf), fp)) {
                if (*buf == '#')
                        continue;
                p = buf;
                name = strsep(&p, "\t\n ");
                if (p) while (isspace(*p)) p++;
                oid = strsep(&p, "\t\n ");
                if (p) while (isspace(*p)) p++;
                lib = strsep(&p, "\t\n ");
                if (p) while (isspace(*p)) p++;
                kobj = strsep(&p, "\t\n ");
                if (!name || !oid || !lib || !kobj)
                        continue;

                if (strcmp(kobj, "-")) {
                        /*
                         * Attempt to load the kernel module if its
                         * not already present.
                         */
                        if (modfind(kobj) < 0) {
                                if (kldload(kobj) < 0) {
                                        fprintf(stderr,
                        "%s: can't find or load kernel module %s for %s\n",
                                            getprogname(), kobj, name);
                                }
                        }
                }
        }
        fclose(fp);
}

static void *
gssd_find_resource(uint64_t id)
{
        struct gss_resource *gr;

        if (!id)
                return (NULL);

        LIST_FOREACH(gr, &gss_resources, gr_link)
                if (gr->gr_id == id)
                        return (gr->gr_res);

        return (NULL);
}

static uint64_t
gssd_make_resource(void *res)
{
        struct gss_resource *gr;

        if (!res)
                return (0);

        gr = malloc(sizeof(struct gss_resource));
        if (!gr)
                return (0);
        gr->gr_id = (gss_next_id++) + ((uint64_t) gss_start_time << 32);
        gr->gr_res = res;
        LIST_INSERT_HEAD(&gss_resources, gr, gr_link);
        gss_resource_count++;
        if (debug_level > 1)
                printf("%d resources allocated\n", gss_resource_count);

        return (gr->gr_id);
}

static void
gssd_delete_resource(uint64_t id)
{
        struct gss_resource *gr;

        LIST_FOREACH(gr, &gss_resources, gr_link) {
                if (gr->gr_id == id) {
                        LIST_REMOVE(gr, gr_link);
                        free(gr);
                        gss_resource_count--;
                        if (debug_level > 1)
                                printf("%d resources allocated\n",
                                    gss_resource_count);
                        return;
                }
        }
}

static void
gssd_verbose_out(const char *fmt, ...)
{
        va_list ap;

        if (verbose != 0) {
                va_start(ap, fmt);
                if (debug_level == 0)
                        vsyslog(LOG_INFO | LOG_DAEMON, fmt, ap);
                else
                        vfprintf(stderr, fmt, ap);
                va_end(ap);
        }
}

bool_t
gssd_null_1_svc(void *argp, void *result, struct svc_req *rqstp)
{

        gssd_verbose_out("gssd_null: done\n");
        return (TRUE);
}

#ifndef MK_MITKRB5
bool_t
gssd_init_sec_context_1_svc(init_sec_context_args *argp, init_sec_context_res *result, struct svc_req *rqstp)
{
        gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
        gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
        gss_name_t name = GSS_C_NO_NAME;
        char ccname[PATH_MAX + 5 + 1], *cp, *cp2;
        int gotone, gotcred;
        OM_uint32 min_stat;
        gss_buffer_desc principal_desc;
        char enctype[sizeof(uint32_t)];
        int key_enctype;
        OM_uint32 maj_stat;

        memset(result, 0, sizeof(*result));
        if (hostbased_initiator_cred != 0 && argp->cred != 0 &&
            argp->uid == 0) {
                /*
                 * These credentials are for a host based initiator name
                 * in a keytab file, which should now have credentials
                 * in /tmp/krb5cc_gssd, because gss_acquire_cred() did
                 * the equivalent of "kinit -k".
                 */
                snprintf(ccname, sizeof(ccname), "FILE:%s",
                    GSSD_CREDENTIAL_CACHE_FILE);
        } else if (ccfile_dirlist[0] != '\0' && argp->cred == 0) {
                /*
                 * For the "-s" case and no credentials provided as an
                 * argument, search the directory list for an appropriate
                 * credential cache file. If the search fails, return failure.
                 */
                gotone = 0;
                cp = ccfile_dirlist;
                do {
                        cp2 = strchr(cp, ':');
                        if (cp2 != NULL)
                                *cp2 = '\0';
                        gotone = find_ccache_file(cp, argp->uid, ccname);
                        if (gotone != 0)
                                break;
                        if (cp2 != NULL)
                                *cp2++ = ':';
                        cp = cp2;
                } while (cp != NULL && *cp != '\0');
                if (gotone == 0) {
                        result->major_status = GSS_S_CREDENTIALS_EXPIRED;
                        gssd_verbose_out("gssd_init_sec_context: -s no"
                            " credential cache file found for uid=%d\n",
                            (int)argp->uid);
                        return (TRUE);
                }
        } else {
                /*
                 * If there wasn't a "-s" option or the credentials have
                 * been provided as an argument, do it the old way.
                 * When credentials are provided, the uid should be root.
                 */
                if (argp->cred != 0 && argp->uid != 0) {
                        if (debug_level == 0)
                                syslog(LOG_ERR, "gss_init_sec_context:"
                                    " cred for non-root");
                        else
                                fprintf(stderr, "gss_init_sec_context:"
                                    " cred for non-root\n");
                }
                snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d",
                    (int) argp->uid);
        }
        setenv("KRB5CCNAME", ccname, TRUE);

        if (argp->cred) {
                cred = gssd_find_resource(argp->cred);
                if (!cred) {
                        result->major_status = GSS_S_CREDENTIALS_EXPIRED;
                        gssd_verbose_out("gssd_init_sec_context: cred"
                            " resource not found\n");
                        return (TRUE);
                }
        }
        if (argp->ctx) {
                ctx = gssd_find_resource(argp->ctx);
                if (!ctx) {
                        result->major_status = GSS_S_CONTEXT_EXPIRED;
                        gssd_verbose_out("gssd_init_sec_context: context"
                            " resource not found\n");
                        return (TRUE);
                }
        }
        if (argp->name) {
                name = gssd_find_resource(argp->name);
                if (!name) {
                        result->major_status = GSS_S_BAD_NAME;
                        gssd_verbose_out("gssd_init_sec_context: name"
                            " resource not found\n");
                        return (TRUE);
                }
        }
        gotcred = 0;

        result->major_status = gss_init_sec_context(&result->minor_status,
            cred, &ctx, name, argp->mech_type,
            argp->req_flags, argp->time_req, argp->input_chan_bindings,
            &argp->input_token, &result->actual_mech_type,
            &result->output_token, &result->ret_flags, &result->time_rec);
        gssd_verbose_out("gssd_init_sec_context: done major=0x%x minor=%d"
            " uid=%d\n", (unsigned int)result->major_status,
            (int)result->minor_status, (int)argp->uid);
        if (gotcred != 0)
                gss_release_cred(&min_stat, &cred);

        if (result->major_status == GSS_S_COMPLETE
            || result->major_status == GSS_S_CONTINUE_NEEDED) {
                if (argp->ctx)
                        result->ctx = argp->ctx;
                else
                        result->ctx = gssd_make_resource(ctx);
        }

        return (TRUE);
}

bool_t
gssd_supports_lucid_1_svc(void *argp, supports_lucid_res *result, struct svc_req *rqstp)
{

        gssd_verbose_out("gssd_lucid: done\n");
        result->major_status = GSS_S_UNAVAILABLE;
        return (TRUE);
}

bool_t
gssd_init_sec_context_lucid_v1_1_svc(init_sec_context_lucid_v1_args *argp,
    init_sec_context_lucid_v1_res *result, struct svc_req *rqstp)
{

        gssd_verbose_out("gssd_init_sec_context_lucid_v1: Heimdal\n");
        result->major_status = GSS_S_UNAVAILABLE;
        return (TRUE);
}

bool_t
gssd_accept_sec_context_lucid_v1_1_svc(accept_sec_context_lucid_v1_args *argp,
    accept_sec_context_lucid_v1_res *result, struct svc_req *rqstp)
{

        gssd_verbose_out("gssd_accept_sec_context_lucid_v1: Heimdal\n");
        result->major_status = GSS_S_UNAVAILABLE;
        return (TRUE);
}

bool_t
gssd_accept_sec_context_1_svc(accept_sec_context_args *argp, accept_sec_context_res *result, struct svc_req *rqstp)
{
        gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
        gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
        gss_name_t src_name;
        gss_cred_id_t delegated_cred_handle;

        memset(result, 0, sizeof(*result));
        if (argp->ctx) {
                ctx = gssd_find_resource(argp->ctx);
                if (!ctx) {
                        result->major_status = GSS_S_CONTEXT_EXPIRED;
                        gssd_verbose_out("gssd_accept_sec_context: ctx"
                            " resource not found\n");
                        return (TRUE);
                }
        }
        if (argp->cred) {
                cred = gssd_find_resource(argp->cred);
                if (!cred) {
                        result->major_status = GSS_S_CREDENTIALS_EXPIRED;
                        gssd_verbose_out("gssd_accept_sec_context: cred"
                            " resource not found\n");
                        return (TRUE);
                }
        }

        memset(result, 0, sizeof(*result));
        result->major_status = gss_accept_sec_context(&result->minor_status,
            &ctx, cred, &argp->input_token, argp->input_chan_bindings,
            &src_name, &result->mech_type, &result->output_token,
            &result->ret_flags, &result->time_rec,
            &delegated_cred_handle);
        gssd_verbose_out("gssd_accept_sec_context: done major=0x%x minor=%d\n",
            (unsigned int)result->major_status, (int)result->minor_status);

        if (result->major_status == GSS_S_COMPLETE
            || result->major_status == GSS_S_CONTINUE_NEEDED) {
                if (argp->ctx)
                        result->ctx = argp->ctx;
                else
                        result->ctx = gssd_make_resource(ctx);
                result->src_name = gssd_make_resource(src_name);
                result->delegated_cred_handle =
                        gssd_make_resource(delegated_cred_handle);
        }

        return (TRUE);
}
#else   /* MK_MITKRB5 */
bool_t
gssd_supports_lucid_1_svc(void *argp, supports_lucid_res *result, struct svc_req *rqstp)
{

        gssd_verbose_out("gssd_lucid: done\n");
        result->vers = 1;
        result->major_status = GSS_S_COMPLETE;
        return (TRUE);
}

bool_t
gssd_init_sec_context_1_svc(init_sec_context_args *argp,
    init_sec_context_res *result, struct svc_req *rqstp)
{

        gssd_verbose_out("gssd_init_sec_context: MIT\n");
        result->major_status = GSS_S_UNAVAILABLE;
        return (TRUE);
}

bool_t
gssd_accept_sec_context_1_svc(accept_sec_context_args *argp,
    accept_sec_context_res *result, struct svc_req *rqstp)
{

        gssd_verbose_out("gssd_accept_sec_context: MIT\n");
        result->major_status = GSS_S_UNAVAILABLE;
        return (TRUE);
}

bool_t
gssd_init_sec_context_lucid_v1_1_svc(init_sec_context_lucid_v1_args *argp,
    init_sec_context_lucid_v1_res *result, struct svc_req *rqstp)
{
        gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
        gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
        gss_name_t name = GSS_C_NO_NAME;
        char ccname[PATH_MAX + 5 + 1], *cp, *cp2;
        int gotone, gotcred;
        OM_uint32 min_stat;
        gss_buffer_desc principal_desc;
        char enctype[sizeof(uint32_t)];
        int key_enctype;
        OM_uint32 maj_stat;

        memset(result, 0, sizeof(*result));
        if (hostbased_initiator_cred != 0 && argp->cred != 0 &&
            argp->uid == 0) {
                /*
                 * These credentials are for a host based initiator name
                 * in a keytab file, which should now have credentials
                 * in /tmp/krb5cc_gssd, because gss_acquire_cred() did
                 * the equivalent of "kinit -k".
                 */
                snprintf(ccname, sizeof(ccname), "FILE:%s",
                    GSSD_CREDENTIAL_CACHE_FILE);
        } else if (ccfile_dirlist[0] != '\0' && argp->cred == 0) {
                /*
                 * For the "-s" case and no credentials provided as an
                 * argument, search the directory list for an appropriate
                 * credential cache file. If the search fails, return failure.
                 */
                gotone = 0;
                cp = ccfile_dirlist;
                do {
                        cp2 = strchr(cp, ':');
                        if (cp2 != NULL)
                                *cp2 = '\0';
                        gotone = find_ccache_file(cp, argp->uid, ccname);
                        if (gotone != 0)
                                break;
                        if (cp2 != NULL)
                                *cp2++ = ':';
                        cp = cp2;
                } while (cp != NULL && *cp != '\0');
                if (gotone == 0) {
                        result->major_status = GSS_S_CREDENTIALS_EXPIRED;
                        gssd_verbose_out("gssd_init_sec_context_plus: -s no"
                            " credential cache file found for uid=%d\n",
                            (int)argp->uid);
                        return (TRUE);
                }
        } else {
                /*
                 * If there wasn't a "-s" option or the credentials have
                 * been provided as an argument, do it the old way.
                 * When credentials are provided, the uid should be root.
                 */
                if (argp->cred != 0 && argp->uid != 0) {
                        if (debug_level == 0)
                                syslog(LOG_ERR, "gss_init_sec_context_plus:"
                                    " cred for non-root");
                        else
                                fprintf(stderr, "gss_init_sec_context_plus:"
                                    " cred for non-root\n");
                }
                snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d",
                    (int) argp->uid);
        }
        setenv("KRB5CCNAME", ccname, TRUE);

        if (argp->cred) {
                cred = gssd_find_resource(argp->cred);
                if (!cred) {
                        result->major_status = GSS_S_CREDENTIALS_EXPIRED;
                        gssd_verbose_out("gssd_init_sec_context_plus: cred"
                            " resource not found\n");
                        return (TRUE);
                }
        }
        if (argp->ctx) {
                ctx = gssd_find_resource(argp->ctx);
                if (!ctx) {
                        result->major_status = GSS_S_CONTEXT_EXPIRED;
                        gssd_verbose_out("gssd_init_sec_context_plus: context"
                            " resource not found\n");
                        return (TRUE);
                }
        }
        if (argp->name) {
                name = gssd_find_resource(argp->name);
                if (!name) {
                        result->major_status = GSS_S_BAD_NAME;
                        gssd_verbose_out("gssd_init_sec_context_plus: name"
                            " resource not found\n");
                        return (TRUE);
                }
        }
        gotcred = 0;

        result->major_status = gss_init_sec_context(&result->minor_status,
            cred, &ctx, name, argp->mech_type,
            argp->req_flags, argp->time_req, argp->input_chan_bindings,
            &argp->input_token, &result->actual_mech_type,
            &result->output_token, &result->ret_flags, &result->time_rec);
        gssd_verbose_out("gssd_init_sec_context_plus: done major=0x%x minor=%d"
            " uid=%d\n", (unsigned int)result->major_status,
            (int)result->minor_status, (int)argp->uid);
        if (gotcred != 0)
                gss_release_cred(&min_stat, &cred);

        if (result->actual_mech_type) {
                /*
                 * Just to keep the bogus "elements" pointer
                 * from core dumping the daemon when linked to MIT
                 * libraries.  For some reason, the "elements" pointer
                 * in actual_mech_type cannot be read.
                 */
                result->actual_mech_type = GSS_KRB5_MECH_OID_X;
        }

        if (result->major_status == GSS_S_COMPLETE
            || result->major_status == GSS_S_CONTINUE_NEEDED) {
                if (argp->ctx)
                        result->ctx = argp->ctx;
                else
                        result->ctx = gssd_make_resource(ctx);
        }

        if (result->major_status == GSS_S_COMPLETE) {
                gss_krb5_lucid_context_v1_t *lctx;

                result->major_status = gss_krb5_export_lucid_sec_context(
                    &result->minor_status, &ctx, 1, (void *)&lctx);
                gssd_delete_resource(result->ctx);
                if (result->major_status == GSS_S_COMPLETE &&
                    lctx != NULL) {
                        result->lucid.initiate = lctx->initiate;
                        result->lucid.endtime = lctx->endtime;
                        result->lucid.send_seq = lctx->send_seq;
                        result->lucid.recv_seq = lctx->recv_seq;
                        result->lucid.protocol = lctx->protocol;
                        if (lctx->protocol == 0) {
                                result->lucid.rfc_sign =
                                    lctx->rfc1964_kd.sign_alg;
                                result->lucid.rfc_seal =
                                    lctx->rfc1964_kd.seal_alg;
                                result->lucid.ctx_type =
                                    lctx->rfc1964_kd.ctx_key.type;
                                result->lucid.ctx_key.length =
                                    lctx->rfc1964_kd.ctx_key.length;
                                result->lucid.ctx_key.value =
                                    mem_alloc(result->lucid.ctx_key.length);
                                memcpy(result->lucid.ctx_key.value,
                                    lctx->rfc1964_kd.ctx_key.data,
                                    result->lucid.ctx_key.length);
                        } else if (lctx->protocol == 1) {
                                result->lucid.have_subkey =
                                    lctx->cfx_kd.have_acceptor_subkey;
                                result->lucid.ctx_type =
                                    lctx->cfx_kd.ctx_key.type;
                                result->lucid.ctx_key.length =
                                    lctx->cfx_kd.ctx_key.length;
                                result->lucid.ctx_key.value =
                                    mem_alloc(result->lucid.ctx_key.length);
                                memcpy(result->lucid.ctx_key.value,
                                    lctx->cfx_kd.ctx_key.data,
                                    result->lucid.ctx_key.length);
                                if (result->lucid.have_subkey != 0) {
                                        result->lucid.subkey_type =
                                            lctx->cfx_kd.acceptor_subkey.type;
                                        result->lucid.subkey_key.length =
                                            lctx->cfx_kd.acceptor_subkey.length;
                                        result->lucid.subkey_key.value =
                                            mem_alloc(
                                            result->lucid.subkey_key.length);
                                        memcpy(result->lucid.subkey_key.value,
                                            lctx->cfx_kd.acceptor_subkey.data,
                                            result->lucid.subkey_key.length);
                                } else {
                                        result->lucid.subkey_type = 0;
                                        result->lucid.subkey_key.length = 0;
                                        result->lucid.subkey_key.value = NULL;
                                }
                        }
                        (void)gss_krb5_free_lucid_sec_context(&min_stat,
                            (void *)lctx);
                } else {
                        gssd_verbose_out("gss_krb5_export_lucid_set_context"
                            " failed: major=0x%x minor=%d lctx=%p\n",
                            result->major_status, result->minor_status, lctx);
                }
        }

        return (TRUE);
}

/*
 * Internal function to acquire unix credentials.
 */
static OM_uint32
_gss_get_unix_cred(OM_uint32 *minor_stat, gss_name_t name, gss_OID mech,
    uid_t *uidp, gid_t *gidp, int *numgroups, gid_t *groups)
{
        OM_uint32 major_stat;
        uid_t uid;
        char buf[1024], *bufp;
        struct passwd pwd, *pw;
        size_t buflen;
        int error;
        static size_t buflen_hint = 1024;

        major_stat = gss_pname_to_uid(minor_stat, name, mech, &uid);
        if (major_stat == GSS_S_COMPLETE) {
                *uidp = uid;
                buflen = buflen_hint;
                for (;;) {
                        pw = NULL;
                        bufp = buf;
                        if (buflen > sizeof(buf))
                                bufp = malloc(buflen);
                        if (bufp == NULL)
                                break;
                        error = getpwuid_r(uid, &pwd, bufp, buflen,
                            &pw);
                        if (error != ERANGE)
                                break;
                        if (buflen > sizeof(buf))
                                free(bufp);
                        buflen += 1024;
                        if (buflen > buflen_hint)
                                buflen_hint = buflen;
                }
                if (pw) {
                        *gidp = pw->pw_gid;
                        getgrouplist(pw->pw_name, pw->pw_gid,
                            groups, numgroups);
                } else {
                        major_stat = GSS_S_FAILURE;
                        gssd_verbose_out("get_unix_cred: cannot find"
                            " passwd entry\n");
                }
                if (bufp != NULL && buflen > sizeof(buf))
                        free(bufp);
        } else if (major_stat != GSS_S_UNAVAILABLE) {
                gssd_verbose_out("gssd_pname_to_uid: failed major=0x%x"
                    " minor=%d\n", major_stat, *minor_stat);
        }
        return (major_stat);
}

bool_t
gssd_accept_sec_context_lucid_v1_1_svc(accept_sec_context_lucid_v1_args *argp,
    accept_sec_context_lucid_v1_res *result, struct svc_req *rqstp)
{
        gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
        gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
        gss_name_t src_name;
        gss_cred_id_t delegated_cred_handle;
        OM_uint32 min_stat;

        memset(result, 0, sizeof(*result));
        if (argp->ctx) {
                ctx = gssd_find_resource(argp->ctx);
                if (!ctx) {
                        result->major_status = GSS_S_CONTEXT_EXPIRED;
                        gssd_verbose_out("gssd_accept_sec_context: ctx"
                            " resource not found\n");
                        return (TRUE);
                }
        }
        if (argp->cred) {
                cred = gssd_find_resource(argp->cred);
                if (!cred) {
                        result->major_status = GSS_S_CREDENTIALS_EXPIRED;
                        gssd_verbose_out("gssd_accept_sec_context: cred"
                            " resource not found\n");
                        return (TRUE);
                }
        }

        memset(result, 0, sizeof(*result));
        result->major_status = gss_accept_sec_context(&result->minor_status,
            &ctx, cred, &argp->input_token, argp->input_chan_bindings,
            &src_name, &result->mech_type, &result->output_token,
            &result->ret_flags, &result->time_rec,
            &delegated_cred_handle);
        gssd_verbose_out("gssd_accept_sec_context: done major=0x%x minor=%d\n",
            (unsigned int)result->major_status, (int)result->minor_status);

        if (result->major_status == GSS_S_COMPLETE
            || result->major_status == GSS_S_CONTINUE_NEEDED) {
                if (argp->ctx)
                        result->ctx = argp->ctx;
                else
                        result->ctx = gssd_make_resource(ctx);
                result->src_name = gssd_make_resource(src_name);
                result->delegated_cred_handle =
                        gssd_make_resource(delegated_cred_handle);
        }

        if (result->major_status == GSS_S_COMPLETE) {
                gss_krb5_lucid_context_v1_t *lctx;

                /* Get the lucid context stuff. */
                result->major_status = gss_krb5_export_lucid_sec_context(
                    &result->minor_status, &ctx, 1, (void *)&lctx);
                gssd_delete_resource(result->ctx);
                if (result->major_status == GSS_S_COMPLETE &&
                    lctx != NULL) {
                        result->lucid.initiate = lctx->initiate;
                        result->lucid.endtime = lctx->endtime;
                        result->lucid.send_seq = lctx->send_seq;
                        result->lucid.recv_seq = lctx->recv_seq;
                        result->lucid.protocol = lctx->protocol;
                        if (lctx->protocol == 0) {
                                result->lucid.rfc_sign =
                                    lctx->rfc1964_kd.sign_alg;
                                result->lucid.rfc_seal =
                                    lctx->rfc1964_kd.seal_alg;
                                result->lucid.ctx_type =
                                    lctx->rfc1964_kd.ctx_key.type;
                                result->lucid.ctx_key.length =
                                    lctx->rfc1964_kd.ctx_key.length;
                                result->lucid.ctx_key.value =
                                    mem_alloc(result->lucid.ctx_key.length);
                                memcpy(result->lucid.ctx_key.value,
                                    lctx->rfc1964_kd.ctx_key.data,
                                    result->lucid.ctx_key.length);
                        } else if (lctx->protocol == 1) {
                                result->lucid.have_subkey =
                                    lctx->cfx_kd.have_acceptor_subkey;
                                result->lucid.ctx_type =
                                    lctx->cfx_kd.ctx_key.type;
                                result->lucid.ctx_key.length =
                                    lctx->cfx_kd.ctx_key.length;
                                result->lucid.ctx_key.value =
                                    mem_alloc(result->lucid.ctx_key.length);
                                memcpy(result->lucid.ctx_key.value,
                                    lctx->cfx_kd.ctx_key.data,
                                    result->lucid.ctx_key.length);
                                if (result->lucid.have_subkey != 0) {
                                        result->lucid.subkey_type =
                                            lctx->cfx_kd.acceptor_subkey.type;
                                        result->lucid.subkey_key.length =
                                            lctx->cfx_kd.acceptor_subkey.length;
                                        result->lucid.subkey_key.value =
                                            mem_alloc(
                                            result->lucid.subkey_key.length);
                                        memcpy(result->lucid.subkey_key.value,
                                            lctx->cfx_kd.acceptor_subkey.data,
                                            result->lucid.subkey_key.length);
                                } else {
                                        result->lucid.subkey_type = 0;
                                        result->lucid.subkey_key.length = 0;
                                        result->lucid.subkey_key.value = NULL;
                                }
                        }
                        (void)gss_krb5_free_lucid_sec_context(&min_stat,
                            (void *)lctx);
                } else {
                        gssd_verbose_out("gss_krb5_export_lucid_set_context"
                            " failed: major=0x%x minor=%d lctx=%p\n",
                            result->major_status, result->minor_status, lctx);
                }

                /* Now, get the exported name. */
                if (result->major_status == GSS_S_COMPLETE) {
                        result->major_status = gss_export_name(
                            &result->minor_status, src_name,
                            &result->exported_name);
                        gssd_verbose_out("gssd_accept_sec_context (name):"
                            " done major=0x%x minor=%d\n",
                            result->major_status, result->minor_status);
                }

                /* Finally, get the unix credentials. */
                if (result->major_status == GSS_S_COMPLETE) {
                        gid_t groups[NGROUPS];
                        int i, len = NGROUPS;
                        OM_uint32 major_stat, minor_stat;

                        major_stat = _gss_get_unix_cred(&minor_stat,
                            src_name, result->mech_type,
                            &result->uid, &result->gid, &len, groups);
                        if (major_stat == GSS_S_COMPLETE) {
                                result->gidlist.gidlist_len = len;
                                result->gidlist.gidlist_val =
                                    mem_alloc(len * sizeof(uint32_t));
                                /*
                                 * Just in case
                                 * sizeof(gid_t) != sizeof(uint32_t).
                                 */
                                for (i = 0; i < len; i++)
                                        result->gidlist.gidlist_val[i] =
                                            groups[i];
                        } else {
                                result->gid = 65534;
                                result->gidlist.gidlist_len = 0;
                                result->gidlist.gidlist_val = NULL;
                                gssd_verbose_out("gssd_pname_to_uid: mapped"
                                    " to uid=%d, but no groups\n",
                                    (int)result->uid);
                        }
                }
        }
        return (TRUE);
}
#endif  /* !MK_MITKRB5 */

bool_t
gssd_delete_sec_context_1_svc(delete_sec_context_args *argp, delete_sec_context_res *result, struct svc_req *rqstp)
{
        gss_ctx_id_t ctx = gssd_find_resource(argp->ctx);

        if (ctx) {
                result->major_status = gss_delete_sec_context(
                        &result->minor_status, &ctx, &result->output_token);
                gssd_delete_resource(argp->ctx);
        } else {
                result->major_status = GSS_S_COMPLETE;
                result->minor_status = 0;
        }
        gssd_verbose_out("gssd_delete_sec_context: done major=0x%x minor=%d\n",
            (unsigned int)result->major_status, (int)result->minor_status);

        return (TRUE);
}

bool_t
gssd_export_sec_context_1_svc(export_sec_context_args *argp, export_sec_context_res *result, struct svc_req *rqstp)
{
        gss_ctx_id_t ctx = gssd_find_resource(argp->ctx);

        if (ctx) {
                result->major_status = gss_export_sec_context(
                        &result->minor_status, &ctx,
                        &result->interprocess_token);
                result->format = KGSS_HEIMDAL_1_1;
                gssd_delete_resource(argp->ctx);
        } else {
                result->major_status = GSS_S_FAILURE;
                result->minor_status = 0;
                result->interprocess_token.length = 0;
                result->interprocess_token.value = NULL;
        }
        gssd_verbose_out("gssd_export_sec_context: done major=0x%x minor=%d\n",
            (unsigned int)result->major_status, (int)result->minor_status);

        return (TRUE);
}

bool_t
gssd_import_name_1_svc(import_name_args *argp, import_name_res *result, struct svc_req *rqstp)
{
        gss_name_t name;

        result->major_status = gss_import_name(&result->minor_status,
            &argp->input_name_buffer, argp->input_name_type, &name);
        gssd_verbose_out("gssd_import_name: done major=0x%x minor=%d\n",
            (unsigned int)result->major_status, (int)result->minor_status);

        if (result->major_status == GSS_S_COMPLETE)
                result->output_name = gssd_make_resource(name);
        else
                result->output_name = 0;

        return (TRUE);
}

/*
 * If the name is a numeric IP host address, do a DNS lookup on it and
 * return the DNS name in a malloc'd string.
 */
static char *
gssd_conv_ip_to_dns(int len, char *name)
{
        struct sockaddr_in sin;
        struct sockaddr_in6 sin6;
        char *retcp;

        retcp = NULL;
        if (len > 0) {
                retcp = mem_alloc(NI_MAXHOST);
                memcpy(retcp, name, len);
                retcp[len] = '\0';
                if (inet_pton(AF_INET, retcp, &sin.sin_addr) != 0) {
                        sin.sin_family = AF_INET;
                        sin.sin_len = sizeof(sin);
                        sin.sin_port = 0;
                        if (getnameinfo((struct sockaddr *)&sin,
                            sizeof(sin), retcp, NI_MAXHOST,
                            NULL, 0, NI_NAMEREQD) != 0) {
                                mem_free(retcp, NI_MAXHOST);
                                return (NULL);
                        }
                } else if (inet_pton(AF_INET6, retcp, &sin6.sin6_addr) != 0) {
                        sin6.sin6_family = AF_INET6;
                        sin6.sin6_len = sizeof(sin6);
                        sin6.sin6_port = 0;
                        if (getnameinfo((struct sockaddr *)&sin6,
                            sizeof(sin6), retcp, NI_MAXHOST,
                            NULL, 0, NI_NAMEREQD) != 0) {
                                mem_free(retcp, NI_MAXHOST);
                                return (NULL);
                        }
                } else {
                        mem_free(retcp, NI_MAXHOST);
                        return (NULL);
                }
                gssd_verbose_out("gssd_conv_ip_to_dns: %s\n", retcp);
        }
        return (retcp);
}

bool_t
gssd_canonicalize_name_1_svc(canonicalize_name_args *argp, canonicalize_name_res *result, struct svc_req *rqstp)
{
        gss_name_t name = gssd_find_resource(argp->input_name);
        gss_name_t output_name;

        memset(result, 0, sizeof(*result));
        if (!name) {
                result->major_status = GSS_S_BAD_NAME;
                return (TRUE);
        }

        result->major_status = gss_canonicalize_name(&result->minor_status,
            name, argp->mech_type, &output_name);
        gssd_verbose_out("gssd_canonicalize_name: done major=0x%x minor=%d\n",
            (unsigned int)result->major_status, (int)result->minor_status);

        if (result->major_status == GSS_S_COMPLETE)
                result->output_name = gssd_make_resource(output_name);
        else
                result->output_name = 0;

        return (TRUE);
}

bool_t
gssd_export_name_1_svc(export_name_args *argp, export_name_res *result, struct svc_req *rqstp)
{
        gss_name_t name = gssd_find_resource(argp->input_name);

        memset(result, 0, sizeof(*result));
        if (!name) {
                result->major_status = GSS_S_BAD_NAME;
                gssd_verbose_out("gssd_export_name: name resource not found\n");
                return (TRUE);
        }

        result->major_status = gss_export_name(&result->minor_status,
            name, &result->exported_name);
        gssd_verbose_out("gssd_export_name: done major=0x%x minor=%d\n",
            (unsigned int)result->major_status, (int)result->minor_status);

        return (TRUE);
}

bool_t
gssd_release_name_1_svc(release_name_args *argp, release_name_res *result, struct svc_req *rqstp)
{
        gss_name_t name = gssd_find_resource(argp->input_name);

        if (name) {
                result->major_status = gss_release_name(&result->minor_status,
                    &name);
                gssd_delete_resource(argp->input_name);
        } else {
                result->major_status = GSS_S_COMPLETE;
                result->minor_status = 0;
        }
        gssd_verbose_out("gssd_release_name: done major=0x%x minor=%d\n",
            (unsigned int)result->major_status, (int)result->minor_status);

        return (TRUE);
}

bool_t
gssd_pname_to_uid_1_svc(pname_to_uid_args *argp, pname_to_uid_res *result, struct svc_req *rqstp)
{
        gss_name_t name = gssd_find_resource(argp->pname);
        uid_t uid;
        char buf[1024], *bufp;
        struct passwd pwd, *pw;
        size_t buflen;
        int error;
        static size_t buflen_hint = 1024;

        memset(result, 0, sizeof(*result));
        if (name) {
                result->major_status =
                        gss_pname_to_uid(&result->minor_status,
                            name, argp->mech, &uid);
                if (result->major_status == GSS_S_COMPLETE) {
                        result->uid = uid;
                        buflen = buflen_hint;
                        for (;;) {
                                pw = NULL;
                                bufp = buf;
                                if (buflen > sizeof(buf))
                                        bufp = malloc(buflen);
                                if (bufp == NULL)
                                        break;
                                error = getpwuid_r(uid, &pwd, bufp, buflen,
                                    &pw);
                                if (error != ERANGE)
                                        break;
                                if (buflen > sizeof(buf))
                                        free(bufp);
                                buflen += 1024;
                                if (buflen > buflen_hint)
                                        buflen_hint = buflen;
                        }
                        if (pw) {
                                int len = NGROUPS;
                                int groups[NGROUPS];
                                result->gid = pw->pw_gid;
                                getgrouplist(pw->pw_name, pw->pw_gid,
                                    groups, &len);
                                result->gidlist.gidlist_len = len;
                                result->gidlist.gidlist_val =
                                        mem_alloc(len * sizeof(int));
                                memcpy(result->gidlist.gidlist_val, groups,
                                    len * sizeof(int));
                                gssd_verbose_out("gssd_pname_to_uid: mapped"
                                    " to uid=%d, gid=%d\n", (int)result->uid,
                                    (int)result->gid);
                        } else {
                                result->gid = 65534;
                                result->gidlist.gidlist_len = 0;
                                result->gidlist.gidlist_val = NULL;
                                gssd_verbose_out("gssd_pname_to_uid: mapped"
                                    " to uid=%d, but no groups\n",
                                    (int)result->uid);
                        }
                        if (bufp != NULL && buflen > sizeof(buf))
                                free(bufp);
                } else
                        gssd_verbose_out("gssd_pname_to_uid: failed major=0x%x"
                            " minor=%d\n", (unsigned int)result->major_status,
                            (int)result->minor_status);
        } else {
                result->major_status = GSS_S_BAD_NAME;
                result->minor_status = 0;
                gssd_verbose_out("gssd_pname_to_uid: no name\n");
        }

        return (TRUE);
}

bool_t
gssd_acquire_cred_1_svc(acquire_cred_args *argp, acquire_cred_res *result, struct svc_req *rqstp)
{
        gss_name_t desired_name = GSS_C_NO_NAME;
        gss_cred_id_t cred;
        char ccname[PATH_MAX + 5 + 1], *cp, *cp2;
        int gotone;
        gss_buffer_desc namebuf;
        uint32_t minstat;
        krb5_error_code kret;

        memset(result, 0, sizeof(*result));
        if (argp->desired_name) {
                desired_name = gssd_find_resource(argp->desired_name);
                if (!desired_name) {
                        result->major_status = GSS_S_BAD_NAME;
                        gssd_verbose_out("gssd_acquire_cred: no desired name"
                            " found\n");
                        return (TRUE);
                }
        }

        if (hostbased_initiator_cred != 0 && argp->desired_name != 0 &&
            argp->uid == 0 && argp->cred_usage == GSS_C_INITIATE) {
                /* This is a host based initiator name in the keytab file. */
                snprintf(ccname, sizeof(ccname), "FILE:%s",
                    GSSD_CREDENTIAL_CACHE_FILE);
                setenv("KRB5CCNAME", ccname, TRUE);
                result->major_status = gss_display_name(&result->minor_status,
                    desired_name, &namebuf, NULL);
                gssd_verbose_out("gssd_acquire_cred: desired name for host "
                    "based initiator cred major=0x%x minor=%d\n",
                    (unsigned int)result->major_status,
                    (int)result->minor_status);
                if (result->major_status != GSS_S_COMPLETE)
                        return (TRUE);
                if (namebuf.length > PATH_MAX + 5) {
                        result->minor_status = 0;
                        result->major_status = GSS_S_FAILURE;
                        return (TRUE);
                }
                memcpy(ccname, namebuf.value, namebuf.length);
                ccname[namebuf.length] = '\0';
                if ((cp = strchr(ccname, '@')) != NULL)
                        *cp = '/';
                kret = gssd_get_cc_from_keytab(ccname);
                gssd_verbose_out("gssd_acquire_cred: using keytab entry for "
                    "%s, kerberos ret=%d\n", ccname, (int)kret);
                gss_release_buffer(&minstat, &namebuf);
                if (kret != 0) {
                        result->minor_status = kret;
                        result->major_status = GSS_S_FAILURE;
                        return (TRUE);
                }
        } else if (ccfile_dirlist[0] != '\0' && argp->desired_name == 0) {
                /*
                 * For the "-s" case and no name provided as an
                 * argument, search the directory list for an appropriate
                 * credential cache file. If the search fails, return failure.
                 */
                gotone = 0;
                cp = ccfile_dirlist;
                do {
                        cp2 = strchr(cp, ':');
                        if (cp2 != NULL)
                                *cp2 = '\0';
                        gotone = find_ccache_file(cp, argp->uid, ccname);
                        if (gotone != 0)
                                break;
                        if (cp2 != NULL)
                                *cp2++ = ':';
                        cp = cp2;
                } while (cp != NULL && *cp != '\0');
                if (gotone == 0) {
                        result->major_status = GSS_S_CREDENTIALS_EXPIRED;
                        gssd_verbose_out("gssd_acquire_cred: no cred cache"
                            " file found\n");
                        return (TRUE);
                }
                setenv("KRB5CCNAME", ccname, TRUE);
        } else {
                /*
                 * If there wasn't a "-s" option or the name has
                 * been provided as an argument, do it the old way.
                 * When a name is provided, it will normally exist in the
                 * default keytab file and the uid will be root.
                 */
                if (argp->desired_name != 0 && argp->uid != 0) {
                        if (debug_level == 0)
                                syslog(LOG_ERR, "gss_acquire_cred:"
                                    " principal_name for non-root");
                        else
                                fprintf(stderr, "gss_acquire_cred:"
                                    " principal_name for non-root\n");
                }
                snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d",
                    (int) argp->uid);
                setenv("KRB5CCNAME", ccname, TRUE);
        }

        result->major_status = gss_acquire_cred(&result->minor_status,
            desired_name, argp->time_req, argp->desired_mechs,
            argp->cred_usage, &cred, &result->actual_mechs, &result->time_rec);
        gssd_verbose_out("gssd_acquire_cred: done major=0x%x minor=%d\n",
            (unsigned int)result->major_status, (int)result->minor_status);

        if (result->major_status == GSS_S_COMPLETE)
                result->output_cred = gssd_make_resource(cred);
        else
                result->output_cred = 0;

        return (TRUE);
}

bool_t
gssd_set_cred_option_1_svc(set_cred_option_args *argp, set_cred_option_res *result, struct svc_req *rqstp)
{
        gss_cred_id_t cred = gssd_find_resource(argp->cred);

        memset(result, 0, sizeof(*result));
        if (!cred) {
                result->major_status = GSS_S_CREDENTIALS_EXPIRED;
                gssd_verbose_out("gssd_set_cred: no credentials\n");
                return (TRUE);
        }

        result->major_status = gss_set_cred_option(&result->minor_status,
            &cred, argp->option_name, &argp->option_value);
        gssd_verbose_out("gssd_set_cred: done major=0x%x minor=%d\n",
            (unsigned int)result->major_status, (int)result->minor_status);

        return (TRUE);
}

bool_t
gssd_release_cred_1_svc(release_cred_args *argp, release_cred_res *result, struct svc_req *rqstp)
{
        gss_cred_id_t cred = gssd_find_resource(argp->cred);

        if (cred) {
                result->major_status = gss_release_cred(&result->minor_status,
                    &cred);
                gssd_delete_resource(argp->cred);
        } else {
                result->major_status = GSS_S_COMPLETE;
                result->minor_status = 0;
        }
        gssd_verbose_out("gssd_release_cred: done major=0x%x minor=%d\n",
            (unsigned int)result->major_status, (int)result->minor_status);

        return (TRUE);
}

bool_t
gssd_display_status_1_svc(display_status_args *argp, display_status_res *result, struct svc_req *rqstp)
{

        result->message_context = argp->message_context;
        result->major_status = gss_display_status(&result->minor_status,
            argp->status_value, argp->status_type, argp->mech_type,
            &result->message_context, &result->status_string);
        gssd_verbose_out("gssd_display_status: done major=0x%x minor=%d\n",
            (unsigned int)result->major_status, (int)result->minor_status);

        return (TRUE);
}

bool_t
gssd_ip_to_dns_1_svc(ip_to_dns_args *argp, ip_to_dns_res *result, struct svc_req *rqstp)
{
        char *host;

        memset(result, 0, sizeof(*result));
        /* Check to see if the name is actually an IP address. */
        host = gssd_conv_ip_to_dns(argp->ip_addr.ip_addr_len,
            argp->ip_addr.ip_addr_val);
        if (host != NULL) {
                result->major_status = GSS_S_COMPLETE;
                result->dns_name.dns_name_len = strlen(host);
                result->dns_name.dns_name_val = host;
                return (TRUE);
        }
        result->major_status = GSS_S_FAILURE;
        return (TRUE);
}

int
gssd_1_freeresult(SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result)
{
        /*
         * We don't use XDR to free the results - anything which was
         * allocated came from GSS-API. We use xdr_result to figure
         * out what to do.
         */
        OM_uint32 junk;

        if (xdr_result == (xdrproc_t) xdr_init_sec_context_res) {
                init_sec_context_res *p = (init_sec_context_res *) result;
                gss_release_buffer(&junk, &p->output_token);
        } else if (xdr_result == (xdrproc_t) xdr_accept_sec_context_res) {
                accept_sec_context_res *p = (accept_sec_context_res *) result;
                gss_release_buffer(&junk, &p->output_token);
        } else if (xdr_result == (xdrproc_t) xdr_delete_sec_context_res) {
                delete_sec_context_res *p = (delete_sec_context_res *) result;
                gss_release_buffer(&junk, &p->output_token);
        } else if (xdr_result == (xdrproc_t) xdr_export_sec_context_res) {
                export_sec_context_res *p = (export_sec_context_res *) result;
                if (p->interprocess_token.length)
                        memset(p->interprocess_token.value, 0,
                            p->interprocess_token.length);
                gss_release_buffer(&junk, &p->interprocess_token);
        } else if (xdr_result == (xdrproc_t) xdr_export_name_res) {
                export_name_res *p = (export_name_res *) result;
                gss_release_buffer(&junk, &p->exported_name);
        } else if (xdr_result == (xdrproc_t) xdr_acquire_cred_res) {
                acquire_cred_res *p = (acquire_cred_res *) result;
                gss_release_oid_set(&junk, &p->actual_mechs);
        } else if (xdr_result == (xdrproc_t) xdr_pname_to_uid_res) {
                pname_to_uid_res *p = (pname_to_uid_res *) result;
                if (p->gidlist.gidlist_val)
                        free(p->gidlist.gidlist_val);
        } else if (xdr_result == (xdrproc_t) xdr_display_status_res) {
                display_status_res *p = (display_status_res *) result;
                gss_release_buffer(&junk, &p->status_string);
        }

        return (TRUE);
}

/*
 * Search a directory for the most likely candidate to be used as the
 * credential cache for a uid. If successful, return 1 and fill the
 * file's path id into "rpath". Otherwise, return 0.
 */
static int
find_ccache_file(const char *dirpath, uid_t uid, char *rpath)
{
        DIR *dirp;
        struct dirent *dp;
        struct stat sb;
        time_t exptime, oexptime;
        int gotone, len, rating, orating;
        char namepath[PATH_MAX + 5 + 1];
        char retpath[PATH_MAX + 5 + 1];

        dirp = opendir(dirpath);
        if (dirp == NULL)
                return (0);
        gotone = 0;
        orating = 0;
        oexptime = 0;
        while ((dp = readdir(dirp)) != NULL) {
                len = snprintf(namepath, sizeof(namepath), "%s/%s", dirpath,
                    dp->d_name);
                if (len < sizeof(namepath) &&
                    (hostbased_initiator_cred == 0 || strcmp(namepath,
                     GSSD_CREDENTIAL_CACHE_FILE) != 0) &&
                    strstr(dp->d_name, ccfile_substring) != NULL &&
                    lstat(namepath, &sb) >= 0 &&
                    sb.st_uid == uid &&
                    S_ISREG(sb.st_mode)) {
                        len = snprintf(namepath, sizeof(namepath), "FILE:%s/%s",
                            dirpath, dp->d_name);
                        if (len < sizeof(namepath) &&
                            is_a_valid_tgt_cache(namepath, uid, &rating,
                            &exptime) != 0) {
                                if (gotone == 0 || rating > orating ||
                                    (rating == orating && exptime > oexptime)) {
                                        orating = rating;
                                        oexptime = exptime;
                                        strcpy(retpath, namepath);
                                        gotone = 1;
                                }
                        }
                }
        }
        closedir(dirp);
        if (gotone != 0) {
                strcpy(rpath, retpath);
                return (1);
        }
        return (0);
}

/*
 * Try to determine if the file is a valid tgt cache file.
 * Check that the file has a valid tgt for a principal.
 * If it does, return 1, otherwise return 0.
 * It also returns a "rating" and the expiry time for the TGT, when found.
 * This "rating" is higher based on heuristics that make it more
 * likely to be the correct credential cache file to use. It can
 * be used by the caller, along with expiry time, to select from
 * multiple credential cache files.
 */
static int
is_a_valid_tgt_cache(const char *filepath, uid_t uid, int *retrating,
    time_t *retexptime)
{
        krb5_context context;
        krb5_principal princ;
        krb5_ccache ccache;
        krb5_error_code retval;
        krb5_cc_cursor curse;
        krb5_creds krbcred;
        int gotone, orating, rating, ret;
        struct passwd *pw;
        char *cp, *cp2, *pname;
        time_t exptime;

        /* Find a likely name for the uid principal. */
        pw = getpwuid(uid);

        /*
         * Do a bunch of krb5 library stuff to try and determine if
         * this file is a credentials cache with an appropriate TGT
         * in it.
         */
        retval = krb5_init_context(&context);
        if (retval != 0)
                return (0);
        retval = krb5_cc_resolve(context, filepath, &ccache);
        if (retval != 0) {
                krb5_free_context(context);
                return (0);
        }
        ret = 0;
        orating = 0;
        exptime = 0;
        retval = krb5_cc_start_seq_get(context, ccache, &curse);
        if (retval == 0) {
                while ((retval = krb5_cc_next_cred(context, ccache, &curse,
                    &krbcred)) == 0) {
                        gotone = 0;
                        rating = 0;
                        retval = krb5_unparse_name(context, krbcred.server,
                            &pname);
                        if (retval == 0) {
                                cp = strchr(pname, '/');
                                if (cp != NULL) {
                                        *cp++ = '\0';
                                        if (strcmp(pname, "krbtgt") == 0 &&
                                            krbcred.times.endtime > time(NULL)
                                            ) {
                                                gotone = 1;
                                                /*
                                                 * Test to see if this is a
                                                 * tgt for cross-realm auth.
                                                 * Rate it higher, if it is not.
                                                 */
                                                cp2 = strchr(cp, '@');
                                                if (cp2 != NULL) {
                                                        *cp2++ = '\0';
                                                        if (strcmp(cp, cp2) ==
                                                            0)
                                                                rating++;
                                                }
                                        }
                                }
                                free(pname);
                        }
                        if (gotone != 0) {
                                retval = krb5_unparse_name(context,
                                    krbcred.client, &pname);
                                if (retval == 0) {
                                        cp = strchr(pname, '@');
                                        if (cp != NULL) {
                                                *cp++ = '\0';
                                                if (pw != NULL && strcmp(pname,
                                                    pw->pw_name) == 0)
                                                        rating++;
                                                if (strchr(pname, '/') == NULL)
                                                        rating++;
                                                if (pref_realm[0] != '\0' &&
                                                    strcmp(cp, pref_realm) == 0)
                                                        rating++;
                                        }
                                }
                                free(pname);
                                if (rating > orating) {
                                        orating = rating;
                                        exptime = krbcred.times.endtime;
                                } else if (rating == orating &&
                                    krbcred.times.endtime > exptime)
                                        exptime = krbcred.times.endtime;
                                ret = 1;
                        }
                        krb5_free_cred_contents(context, &krbcred);
                }
                krb5_cc_end_seq_get(context, ccache, &curse);
        }
        krb5_cc_close(context, ccache);
        krb5_free_context(context);
        if (ret != 0) {
                *retrating = orating;
                *retexptime = exptime;
        }
        return (ret);
}

/*
 * This function attempts to do essentially a "kinit -k" for the principal
 * name provided as the argument, so that there will be a TGT in the
 * credential cache.
 */
static krb5_error_code
gssd_get_cc_from_keytab(const char *name)
{
        krb5_error_code ret, opt_ret, princ_ret, cc_ret, kt_ret, cred_ret;
        krb5_context context;
        krb5_principal principal;
        krb5_keytab kt;
        krb5_creds cred;
        krb5_get_init_creds_opt *opt;
        krb5_deltat start_time = 0;
        krb5_ccache ccache;

        ret = krb5_init_context(&context);
        if (ret != 0)
                return (ret);
        opt_ret = cc_ret = kt_ret = cred_ret = 1;       /* anything non-zero */
        princ_ret = ret = krb5_parse_name(context, name, &principal);
        if (ret == 0)
                opt_ret = ret = krb5_get_init_creds_opt_alloc(context, &opt);
        if (ret == 0)
                cc_ret = ret = krb5_cc_default(context, &ccache);
        if (ret == 0)
                ret = krb5_cc_initialize(context, ccache, principal);
        if (ret == 0) {
#ifndef MK_MITKRB5
                /* For Heimdal only */
                krb5_get_init_creds_opt_set_default_flags(context, "gssd",
                    krb5_principal_get_realm(context, principal), opt);
#endif
                kt_ret = ret = krb5_kt_default(context, &kt);
        }
        if (ret == 0)
                cred_ret = ret = krb5_get_init_creds_keytab(context, &cred,
                    principal, kt, start_time, NULL, opt);
        if (ret == 0)
                ret = krb5_cc_store_cred(context, ccache, &cred);
        if (kt_ret == 0)
                krb5_kt_close(context, kt);
        if (cc_ret == 0)
                krb5_cc_close(context, ccache);
        if (opt_ret == 0)
                krb5_get_init_creds_opt_free(context, opt);
        if (princ_ret == 0)
                krb5_free_principal(context, principal);
        if (cred_ret == 0)
                krb5_free_cred_contents(context, &cred);
        krb5_free_context(context);
        return (ret);
}

/*
 * Acquire a gss credential for a uid.
 */
static OM_uint32
gssd_get_user_cred(OM_uint32 *min_statp, uid_t uid, gss_cred_id_t *credp)
{
        gss_buffer_desc principal_desc;
        gss_name_t name;
        OM_uint32 maj_stat, min_stat;
        gss_OID_set mechlist;
        struct passwd *pw;

        pw = getpwuid(uid);
        if (pw == NULL) {
                *min_statp = 0;
                return (GSS_S_FAILURE);
        }

        /*
         * The mechanism must be set to KerberosV for acquisition
         * of credentials to work reliably.
         */
        maj_stat = gss_create_empty_oid_set(min_statp, &mechlist);
        if (maj_stat != GSS_S_COMPLETE)
                return (maj_stat);
        maj_stat = gss_add_oid_set_member(min_statp, GSS_KRB5_MECH_OID_X,
            &mechlist);
        if (maj_stat != GSS_S_COMPLETE) {
                gss_release_oid_set(&min_stat, &mechlist);
                return (maj_stat);
        }

        principal_desc.value = (void *)pw->pw_name;
        principal_desc.length = strlen(pw->pw_name);
        maj_stat = gss_import_name(min_statp, &principal_desc,
            GSS_C_NT_USER_NAME, &name);
        if (maj_stat != GSS_S_COMPLETE) {
                gss_release_oid_set(&min_stat, &mechlist);
                return (maj_stat);
        }
        /* Acquire the credentials. */
        maj_stat = gss_acquire_cred(min_statp, name, 0, mechlist,
            GSS_C_INITIATE, credp, NULL, NULL);
        gss_release_name(&min_stat, &name);
        gss_release_oid_set(&min_stat, &mechlist);
        return (maj_stat);
}

void gssd_terminate(int sig __unused)
{

        if (hostbased_initiator_cred != 0)
                unlink(GSSD_CREDENTIAL_CACHE_FILE);
        exit(0);
}