root/usr/src/lib/libsmbfs/smb/ssp.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Security Provider glue
 *
 * Modeled after SSPI for now, only because we're currently
 * using the Microsoft sample spnego code.
 *
 * ToDo: Port all of this to GSS-API plugins.
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <netdb.h>
#include <libintl.h>
#include <xti.h>
#include <assert.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/byteorder.h>
#include <sys/socket.h>
#include <sys/fcntl.h>

#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

#include <netsmb/smb_lib.h>
#include <netsmb/mchain.h>

#include "private.h"
#include "charsets.h"
#include "spnego.h"
#include "derparse.h"
#include "ssp.h"


/*
 * ssp_ctx_create_client
 *
 * This is the first function called for SMB "extended security".
 * Here we select a security support provider (SSP), or mechanism,
 * and build the security context used throughout authentication.
 *
 * Note that we receive a "hint" in the SMB Negotiate response
 * that contains the list of mechanisms supported by the server.
 * We use this to help us select a mechanism.
 *
 * With SSPI this would call:
 *      ssp->InitSecurityInterface()
 *      ssp->AcquireCredentialsHandle()
 *      ssp->InitializeSecurityContext()
 * With GSS-API this will become:
 *      gss_import_name(... service_principal_name)
 *      gss_init_sec_context(), etc.
 */
int
ssp_ctx_create_client(struct smb_ctx *ctx, struct mbdata *hint_mb)
{
        struct ssp_ctx *sp;
        mbuf_t *m;
        SPNEGO_MECH_OID oid;
        int indx, rc;
        int err = ENOTSUP; /* in case nothing matches */

        sp = malloc(sizeof (*sp));
        if (sp == NULL)
                return (ENOMEM);
        bzero(sp, sizeof (*sp));
        ctx->ct_ssp_ctx = sp;
        sp->smb_ctx = ctx;

        /*
         * Parse the SPNEGO "hint" to get the server's list of
         * supported mechanisms.  If the "hint" is empty,
         * assume NTLMSSP.  (Or could use "raw NTLMSSP")
         */
        m = hint_mb->mb_top;
        if (m == NULL)
                goto use_ntlm;
        rc = spnegoInitFromBinary((uchar_t *)m->m_data, m->m_len,
            &sp->sp_hint);
        if (rc) {
                DPRINT("parse hint, rc %d", rc);
                goto use_ntlm;
        }

        /*
         * Did the server offer Kerberos?
         * Either spec. OID or legacy is OK,
         * but have to remember what we got.
         */
        oid = spnego_mech_oid_NotUsed;
        if (0 == spnegoIsMechTypeAvailable(sp->sp_hint,
            spnego_mech_oid_Kerberos_V5, &indx))
                oid = spnego_mech_oid_Kerberos_V5;
        else if (0 == spnegoIsMechTypeAvailable(sp->sp_hint,
            spnego_mech_oid_Kerberos_V5_Legacy, &indx))
                oid = spnego_mech_oid_Kerberos_V5_Legacy;
        if (oid != spnego_mech_oid_NotUsed) {
                /*
                 * Yes! Server offers Kerberos.
                 * Try to init our krb5 mechanism.
                 * It will fail if the calling user
                 * does not have krb5 credentials.
                 */
                sp->sp_mech = oid;
                err = krb5ssp_init_client(sp);
                if (err == 0) {
                        DPRINT("using Kerberos");
                        return (0);
                }
                /* else fall back to NTLMSSP */
        }

        /*
         * Did the server offer NTLMSSP?
         */
        if (0 == spnegoIsMechTypeAvailable(sp->sp_hint,
            spnego_mech_oid_NTLMSSP, &indx)) {
                /*
                 * OK, we'll use NTLMSSP
                 */
        use_ntlm:
                sp->sp_mech = spnego_mech_oid_NTLMSSP;
                err = ntlmssp_init_client(sp);
                if (err == 0) {
                        DPRINT("using NTLMSSP");
                        return (0);
                }
        }

        /* No supported mechanisms! */
        return (err);
}


/*
 * ssp_ctx_destroy
 *
 * Dispatch to the mechanism-specific destroy.
 */
void
ssp_ctx_destroy(struct smb_ctx *ctx)
{
        ssp_ctx_t *sp;

        sp = ctx->ct_ssp_ctx;
        ctx->ct_ssp_ctx = NULL;

        if (sp == NULL)
                return;

        if (sp->sp_destroy != NULL)
                (sp->sp_destroy)(sp);

        if (sp->sp_hint != NULL)
                spnegoFreeData(sp->sp_hint);

        free(sp);
}


/*
 * ssp_ctx_next_token
 *
 * This is the function called to generate the next token to send,
 * given a token just received, using the selected back-end method.
 * The back-end method is called a security service provider (SSP).
 *
 * This is also called to generate the first token to send
 * (when called with caller_in == NULL) and to handle the last
 * token received (when called with caller_out == NULL).
 * See caller: smb_ssnsetup_spnego
 *
 * Note that if the back-end SSP "next token" function ever
 * returns an error, the conversation ends, and there are
 * no further calls to this function for this context.
 *
 * General outline of this funcion:
 *      if (caller_in)
 *              Unwrap caller_in spnego blob,
 *              store payload in body_in
 *      Call back-end SSP "next token" method (body_in, body_out)
 *      if (caller_out)
 *              Wrap returned body_out in spnego,
 *              store in caller_out
 *
 * With SSPI this would call:
 *      ssp->InitializeSecurityContext()
 * With GSS-API this will become:
 *      gss_init_sec_context()
 */
int
ssp_ctx_next_token(struct smb_ctx *ctx,
        struct mbdata *caller_in,
        struct mbdata *caller_out)
{
        struct mbdata body_in, body_out;
        SPNEGO_TOKEN_HANDLE stok_in, stok_out;
        SPNEGO_NEGRESULT result;
        ssp_ctx_t *sp;
        struct mbuf *m;
        ulong_t toklen;
        int err, rc;

        bzero(&body_in, sizeof (body_in));
        bzero(&body_out, sizeof (body_out));
        stok_out = stok_in = NULL;
        sp = ctx->ct_ssp_ctx;

        /*
         * If we have an spnego input token, parse it,
         * extract the payload for the back-end SSP.
         */
        if (caller_in != NULL) {

                /*
                 * Let the spnego code parse it.
                 */
                m = caller_in->mb_top;
                rc = spnegoInitFromBinary((uchar_t *)m->m_data,
                    m->m_len, &stok_in);
                if (rc) {
                        DPRINT("parse reply, rc %d", rc);
                        err = EBADRPC;
                        goto out;
                }
                /* Note: Allocated stok_in  */

                /*
                 * Now get the payload.  Two calls:
                 * first gets the size, 2nd the data.
                 *
                 * Expect SPNEGO_E_BUFFER_TOO_SMALL here,
                 * but if the payload is missing, we'll
                 * get SPNEGO_E_ELEMENT_UNAVAILABLE.
                 */
                rc = spnegoGetMechToken(stok_in, NULL, &toklen);
                switch (rc) {
                case SPNEGO_E_ELEMENT_UNAVAILABLE:
                        toklen = 0;
                        break;
                case SPNEGO_E_BUFFER_TOO_SMALL:
                        /* have toklen */
                        break;
                default:
                        DPRINT("GetMechTok1, rc %d", rc);
                        err = EBADRPC;
                        goto out;
                }
                err = mb_init_sz(&body_in, (size_t)toklen);
                if (err)
                        goto out;
                m = body_in.mb_top;
                if (toklen > 0) {
                        rc = spnegoGetMechToken(stok_in,
                            (uchar_t *)m->m_data, &toklen);
                        if (rc) {
                                DPRINT("GetMechTok2, rc %d", rc);
                                err = EBADRPC;
                                goto out;
                        }
                        body_in.mb_count = m->m_len = (size_t)toklen;
                }
        }

        /*
         * Call the back-end security provider (SSP) to
         * handle the received token (if present) and
         * generate an output token (if requested).
         */
        err = sp->sp_nexttok(sp,
            caller_in ? &body_in : NULL,
            caller_out ? &body_out : NULL);
        if (err)
                goto out;

        /*
         * Wrap the outgoing body if requested,
         * either negTokenInit on first call, or
         * negTokenTarg on subsequent calls.
         */
        if (caller_out != NULL) {
                m = body_out.mb_top;

                if (caller_in == NULL) {
                        /*
                         * This is the first call, so create a
                         * negTokenInit.
                         */
                        rc = spnegoCreateNegTokenInit(
                            sp->sp_mech, 0,
                            (uchar_t *)m->m_data, m->m_len,
                            NULL, 0, &stok_out);
                        /* Note: allocated stok_out */
                } else {
                        /*
                         * Note: must pass spnego_mech_oid_NotUsed,
                         * instead of sp->sp_mech so that the spnego
                         * code will not marshal a mech OID list.
                         * The mechanism is determined at this point,
                         * and some servers won't parse an unexpected
                         * mech. OID list in a negTokenTarg
                         */
                        rc = spnegoCreateNegTokenTarg(
                            spnego_mech_oid_NotUsed,
                            spnego_negresult_NotUsed,
                            (uchar_t *)m->m_data, m->m_len,
                            NULL, 0, &stok_out);
                        /* Note: allocated stok_out */
                }
                if (rc) {
                        DPRINT("CreateNegTokenX, rc 0x%x", rc);
                        err = EBADRPC;
                        goto out;
                }

                /*
                 * Copy binary from stok_out to caller_out
                 * Two calls: get the size, get the data.
                 */
                rc = spnegoTokenGetBinary(stok_out, NULL, &toklen);
                if (rc != SPNEGO_E_BUFFER_TOO_SMALL) {
                        DPRINT("GetBinary1, rc 0x%x", rc);
                        err = EBADRPC;
                        goto out;
                }
                err = mb_init_sz(caller_out, (size_t)toklen);
                if (err)
                        goto out;
                m = caller_out->mb_top;
                rc = spnegoTokenGetBinary(stok_out,
                    (uchar_t *)m->m_data, &toklen);
                if (rc) {
                        DPRINT("GetBinary2, rc 0x%x", rc);
                        err = EBADRPC;
                        goto out;
                }
                caller_out->mb_count = m->m_len = (size_t)toklen;
        } else {
                /*
                 * caller_out == NULL, so this is the "final" call.
                 * Get final SPNEGO result from the INPUT token.
                 */
                rc = spnegoGetNegotiationResult(stok_in, &result);
                if (rc) {
                        DPRINT("rc 0x%x", rc);
                        err = EBADRPC;
                        goto out;
                }
                DPRINT("spnego result: 0x%x", result);
                if (result != spnego_negresult_success) {
                        err = EAUTH;
                        goto out;
                }
        }
        err = 0;

out:
        mb_done(&body_in);
        mb_done(&body_out);
        spnegoFreeData(stok_in);
        spnegoFreeData(stok_out);

        return (err);
}