/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); }