1*613a2f6bSGordon Ross /* 2*613a2f6bSGordon Ross * CDDL HEADER START 3*613a2f6bSGordon Ross * 4*613a2f6bSGordon Ross * The contents of this file are subject to the terms of the 5*613a2f6bSGordon Ross * Common Development and Distribution License (the "License"). 6*613a2f6bSGordon Ross * You may not use this file except in compliance with the License. 7*613a2f6bSGordon Ross * 8*613a2f6bSGordon Ross * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9*613a2f6bSGordon Ross * or http://www.opensolaris.org/os/licensing. 10*613a2f6bSGordon Ross * See the License for the specific language governing permissions 11*613a2f6bSGordon Ross * and limitations under the License. 12*613a2f6bSGordon Ross * 13*613a2f6bSGordon Ross * When distributing Covered Code, include this CDDL HEADER in each 14*613a2f6bSGordon Ross * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15*613a2f6bSGordon Ross * If applicable, add the following below this CDDL HEADER, with the 16*613a2f6bSGordon Ross * fields enclosed by brackets "[]" replaced with your own identifying 17*613a2f6bSGordon Ross * information: Portions Copyright [yyyy] [name of copyright owner] 18*613a2f6bSGordon Ross * 19*613a2f6bSGordon Ross * CDDL HEADER END 20*613a2f6bSGordon Ross */ 21*613a2f6bSGordon Ross 22*613a2f6bSGordon Ross /* 23*613a2f6bSGordon Ross * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24*613a2f6bSGordon Ross * Use is subject to license terms. 25*613a2f6bSGordon Ross */ 26*613a2f6bSGordon Ross 27*613a2f6bSGordon Ross /* 28*613a2f6bSGordon Ross * Security Provider glue 29*613a2f6bSGordon Ross * 30*613a2f6bSGordon Ross * Modeled after SSPI for now, only because we're currently 31*613a2f6bSGordon Ross * using the Microsoft sample spnego code. 32*613a2f6bSGordon Ross * 33*613a2f6bSGordon Ross * ToDo: Port all of this to GSS-API plugins. 34*613a2f6bSGordon Ross */ 35*613a2f6bSGordon Ross 36*613a2f6bSGordon Ross #include <errno.h> 37*613a2f6bSGordon Ross #include <stdio.h> 38*613a2f6bSGordon Ross #include <stdlib.h> 39*613a2f6bSGordon Ross #include <unistd.h> 40*613a2f6bSGordon Ross #include <strings.h> 41*613a2f6bSGordon Ross #include <netdb.h> 42*613a2f6bSGordon Ross #include <libintl.h> 43*613a2f6bSGordon Ross #include <xti.h> 44*613a2f6bSGordon Ross #include <assert.h> 45*613a2f6bSGordon Ross 46*613a2f6bSGordon Ross #include <sys/types.h> 47*613a2f6bSGordon Ross #include <sys/time.h> 48*613a2f6bSGordon Ross #include <sys/byteorder.h> 49*613a2f6bSGordon Ross #include <sys/socket.h> 50*613a2f6bSGordon Ross #include <sys/fcntl.h> 51*613a2f6bSGordon Ross 52*613a2f6bSGordon Ross #include <netinet/in.h> 53*613a2f6bSGordon Ross #include <netinet/tcp.h> 54*613a2f6bSGordon Ross #include <arpa/inet.h> 55*613a2f6bSGordon Ross 56*613a2f6bSGordon Ross #include <netsmb/smb_lib.h> 57*613a2f6bSGordon Ross #include <netsmb/mchain.h> 58*613a2f6bSGordon Ross 59*613a2f6bSGordon Ross #include "private.h" 60*613a2f6bSGordon Ross #include "charsets.h" 61*613a2f6bSGordon Ross #include "spnego.h" 62*613a2f6bSGordon Ross #include "derparse.h" 63*613a2f6bSGordon Ross #include "ssp.h" 64*613a2f6bSGordon Ross 65*613a2f6bSGordon Ross 66*613a2f6bSGordon Ross /* 67*613a2f6bSGordon Ross * ssp_ctx_create_client 68*613a2f6bSGordon Ross * 69*613a2f6bSGordon Ross * This is the first function called for SMB "extended security". 70*613a2f6bSGordon Ross * Here we select a security support provider (SSP), or mechanism, 71*613a2f6bSGordon Ross * and build the security context used throughout authentication. 72*613a2f6bSGordon Ross * 73*613a2f6bSGordon Ross * Note that we receive a "hint" in the SMB Negotiate response 74*613a2f6bSGordon Ross * that contains the list of mechanisms supported by the server. 75*613a2f6bSGordon Ross * We use this to help us select a mechanism. 76*613a2f6bSGordon Ross * 77*613a2f6bSGordon Ross * With SSPI this would call: 78*613a2f6bSGordon Ross * ssp->InitSecurityInterface() 79*613a2f6bSGordon Ross * ssp->AcquireCredentialsHandle() 80*613a2f6bSGordon Ross * ssp->InitializeSecurityContext() 81*613a2f6bSGordon Ross * With GSS-API this will become: 82*613a2f6bSGordon Ross * gss_import_name(... service_principal_name) 83*613a2f6bSGordon Ross * gss_init_sec_context(), etc. 84*613a2f6bSGordon Ross */ 85*613a2f6bSGordon Ross int 86*613a2f6bSGordon Ross ssp_ctx_create_client(struct smb_ctx *ctx, struct mbdata *hint_mb) 87*613a2f6bSGordon Ross { 88*613a2f6bSGordon Ross struct ssp_ctx *sp; 89*613a2f6bSGordon Ross mbuf_t *m; 90*613a2f6bSGordon Ross SPNEGO_MECH_OID oid; 91*613a2f6bSGordon Ross int indx, rc; 92*613a2f6bSGordon Ross int err = ENOTSUP; /* in case nothing matches */ 93*613a2f6bSGordon Ross 94*613a2f6bSGordon Ross sp = malloc(sizeof (*sp)); 95*613a2f6bSGordon Ross if (sp == NULL) 96*613a2f6bSGordon Ross return (ENOMEM); 97*613a2f6bSGordon Ross bzero(sp, sizeof (*sp)); 98*613a2f6bSGordon Ross ctx->ct_ssp_ctx = sp; 99*613a2f6bSGordon Ross sp->smb_ctx = ctx; 100*613a2f6bSGordon Ross 101*613a2f6bSGordon Ross /* 102*613a2f6bSGordon Ross * Parse the SPNEGO "hint" to get the server's list of 103*613a2f6bSGordon Ross * supported mechanisms. If the "hint" is empty, 104*613a2f6bSGordon Ross * assume NTLMSSP. (Or could use "raw NTLMSSP") 105*613a2f6bSGordon Ross */ 106*613a2f6bSGordon Ross m = hint_mb->mb_top; 107*613a2f6bSGordon Ross if (m == NULL) 108*613a2f6bSGordon Ross goto use_ntlm; 109*613a2f6bSGordon Ross rc = spnegoInitFromBinary((uchar_t *)m->m_data, m->m_len, 110*613a2f6bSGordon Ross &sp->sp_hint); 111*613a2f6bSGordon Ross if (rc) { 112*613a2f6bSGordon Ross DPRINT("parse hint, rc %d", rc); 113*613a2f6bSGordon Ross goto use_ntlm; 114*613a2f6bSGordon Ross } 115*613a2f6bSGordon Ross 116*613a2f6bSGordon Ross /* 117*613a2f6bSGordon Ross * Did the server offer Kerberos? 118*613a2f6bSGordon Ross * Either spec. OID or legacy is OK, 119*613a2f6bSGordon Ross * but have to remember what we got. 120*613a2f6bSGordon Ross */ 121*613a2f6bSGordon Ross oid = spnego_mech_oid_NotUsed; 122*613a2f6bSGordon Ross if (0 == spnegoIsMechTypeAvailable(sp->sp_hint, 123*613a2f6bSGordon Ross spnego_mech_oid_Kerberos_V5, &indx)) 124*613a2f6bSGordon Ross oid = spnego_mech_oid_Kerberos_V5; 125*613a2f6bSGordon Ross else if (0 == spnegoIsMechTypeAvailable(sp->sp_hint, 126*613a2f6bSGordon Ross spnego_mech_oid_Kerberos_V5_Legacy, &indx)) 127*613a2f6bSGordon Ross oid = spnego_mech_oid_Kerberos_V5_Legacy; 128*613a2f6bSGordon Ross if (oid != spnego_mech_oid_NotUsed) { 129*613a2f6bSGordon Ross /* 130*613a2f6bSGordon Ross * Yes! Server offers Kerberos. 131*613a2f6bSGordon Ross * Try to init our krb5 mechanism. 132*613a2f6bSGordon Ross * It will fail if the calling user 133*613a2f6bSGordon Ross * does not have krb5 credentials. 134*613a2f6bSGordon Ross */ 135*613a2f6bSGordon Ross sp->sp_mech = oid; 136*613a2f6bSGordon Ross err = krb5ssp_init_client(sp); 137*613a2f6bSGordon Ross if (err == 0) { 138*613a2f6bSGordon Ross DPRINT("using Kerberos"); 139*613a2f6bSGordon Ross return (0); 140*613a2f6bSGordon Ross } 141*613a2f6bSGordon Ross /* else fall back to NTLMSSP */ 142*613a2f6bSGordon Ross } 143*613a2f6bSGordon Ross 144*613a2f6bSGordon Ross /* 145*613a2f6bSGordon Ross * Did the server offer NTLMSSP? 146*613a2f6bSGordon Ross */ 147*613a2f6bSGordon Ross if (0 == spnegoIsMechTypeAvailable(sp->sp_hint, 148*613a2f6bSGordon Ross spnego_mech_oid_NTLMSSP, &indx)) { 149*613a2f6bSGordon Ross /* 150*613a2f6bSGordon Ross * OK, we'll use NTLMSSP 151*613a2f6bSGordon Ross */ 152*613a2f6bSGordon Ross use_ntlm: 153*613a2f6bSGordon Ross sp->sp_mech = spnego_mech_oid_NTLMSSP; 154*613a2f6bSGordon Ross err = ntlmssp_init_client(sp); 155*613a2f6bSGordon Ross if (err == 0) { 156*613a2f6bSGordon Ross DPRINT("using NTLMSSP"); 157*613a2f6bSGordon Ross return (0); 158*613a2f6bSGordon Ross } 159*613a2f6bSGordon Ross } 160*613a2f6bSGordon Ross 161*613a2f6bSGordon Ross /* No supported mechanisms! */ 162*613a2f6bSGordon Ross return (err); 163*613a2f6bSGordon Ross } 164*613a2f6bSGordon Ross 165*613a2f6bSGordon Ross 166*613a2f6bSGordon Ross /* 167*613a2f6bSGordon Ross * ssp_ctx_destroy 168*613a2f6bSGordon Ross * 169*613a2f6bSGordon Ross * Dispatch to the mechanism-specific destroy. 170*613a2f6bSGordon Ross */ 171*613a2f6bSGordon Ross void 172*613a2f6bSGordon Ross ssp_ctx_destroy(struct smb_ctx *ctx) 173*613a2f6bSGordon Ross { 174*613a2f6bSGordon Ross ssp_ctx_t *sp; 175*613a2f6bSGordon Ross 176*613a2f6bSGordon Ross sp = ctx->ct_ssp_ctx; 177*613a2f6bSGordon Ross ctx->ct_ssp_ctx = NULL; 178*613a2f6bSGordon Ross 179*613a2f6bSGordon Ross if (sp == NULL) 180*613a2f6bSGordon Ross return; 181*613a2f6bSGordon Ross 182*613a2f6bSGordon Ross if (sp->sp_destroy != NULL) 183*613a2f6bSGordon Ross (sp->sp_destroy)(sp); 184*613a2f6bSGordon Ross 185*613a2f6bSGordon Ross if (sp->sp_hint != NULL) 186*613a2f6bSGordon Ross spnegoFreeData(sp->sp_hint); 187*613a2f6bSGordon Ross 188*613a2f6bSGordon Ross free(sp); 189*613a2f6bSGordon Ross } 190*613a2f6bSGordon Ross 191*613a2f6bSGordon Ross 192*613a2f6bSGordon Ross /* 193*613a2f6bSGordon Ross * ssp_ctx_next_token 194*613a2f6bSGordon Ross * 195*613a2f6bSGordon Ross * This is the function called to generate the next token to send, 196*613a2f6bSGordon Ross * given a token just received, using the selected back-end method. 197*613a2f6bSGordon Ross * The back-end method is called a security service provider (SSP). 198*613a2f6bSGordon Ross * 199*613a2f6bSGordon Ross * This is also called to generate the first token to send 200*613a2f6bSGordon Ross * (when called with caller_in == NULL) and to handle the last 201*613a2f6bSGordon Ross * token received (when called with caller_out == NULL). 202*613a2f6bSGordon Ross * See caller: smb_ssnsetup_spnego 203*613a2f6bSGordon Ross * 204*613a2f6bSGordon Ross * Note that if the back-end SSP "next token" function ever 205*613a2f6bSGordon Ross * returns an error, the conversation ends, and there are 206*613a2f6bSGordon Ross * no further calls to this function for this context. 207*613a2f6bSGordon Ross * 208*613a2f6bSGordon Ross * General outline of this funcion: 209*613a2f6bSGordon Ross * if (caller_in) 210*613a2f6bSGordon Ross * Unwrap caller_in spnego blob, 211*613a2f6bSGordon Ross * store payload in body_in 212*613a2f6bSGordon Ross * Call back-end SSP "next token" method (body_in, body_out) 213*613a2f6bSGordon Ross * if (caller_out) 214*613a2f6bSGordon Ross * Wrap returned body_out in spnego, 215*613a2f6bSGordon Ross * store in caller_out 216*613a2f6bSGordon Ross * 217*613a2f6bSGordon Ross * With SSPI this would call: 218*613a2f6bSGordon Ross * ssp->InitializeSecurityContext() 219*613a2f6bSGordon Ross * With GSS-API this will become: 220*613a2f6bSGordon Ross * gss_init_sec_context() 221*613a2f6bSGordon Ross */ 222*613a2f6bSGordon Ross int 223*613a2f6bSGordon Ross ssp_ctx_next_token(struct smb_ctx *ctx, 224*613a2f6bSGordon Ross struct mbdata *caller_in, 225*613a2f6bSGordon Ross struct mbdata *caller_out) 226*613a2f6bSGordon Ross { 227*613a2f6bSGordon Ross struct mbdata body_in, body_out; 228*613a2f6bSGordon Ross SPNEGO_TOKEN_HANDLE stok_in, stok_out; 229*613a2f6bSGordon Ross SPNEGO_NEGRESULT result; 230*613a2f6bSGordon Ross ssp_ctx_t *sp; 231*613a2f6bSGordon Ross struct mbuf *m; 232*613a2f6bSGordon Ross ulong_t toklen; 233*613a2f6bSGordon Ross int err, rc; 234*613a2f6bSGordon Ross 235*613a2f6bSGordon Ross bzero(&body_in, sizeof (body_in)); 236*613a2f6bSGordon Ross bzero(&body_out, sizeof (body_out)); 237*613a2f6bSGordon Ross stok_out = stok_in = NULL; 238*613a2f6bSGordon Ross sp = ctx->ct_ssp_ctx; 239*613a2f6bSGordon Ross 240*613a2f6bSGordon Ross /* 241*613a2f6bSGordon Ross * If we have an spnego input token, parse it, 242*613a2f6bSGordon Ross * extract the payload for the back-end SSP. 243*613a2f6bSGordon Ross */ 244*613a2f6bSGordon Ross if (caller_in != NULL) { 245*613a2f6bSGordon Ross 246*613a2f6bSGordon Ross /* 247*613a2f6bSGordon Ross * Let the spnego code parse it. 248*613a2f6bSGordon Ross */ 249*613a2f6bSGordon Ross m = caller_in->mb_top; 250*613a2f6bSGordon Ross rc = spnegoInitFromBinary((uchar_t *)m->m_data, 251*613a2f6bSGordon Ross m->m_len, &stok_in); 252*613a2f6bSGordon Ross if (rc) { 253*613a2f6bSGordon Ross DPRINT("parse reply, rc %d", rc); 254*613a2f6bSGordon Ross err = EBADRPC; 255*613a2f6bSGordon Ross goto out; 256*613a2f6bSGordon Ross } 257*613a2f6bSGordon Ross /* Note: Allocated stok_in */ 258*613a2f6bSGordon Ross 259*613a2f6bSGordon Ross /* 260*613a2f6bSGordon Ross * Now get the payload. Two calls: 261*613a2f6bSGordon Ross * first gets the size, 2nd the data. 262*613a2f6bSGordon Ross * 263*613a2f6bSGordon Ross * Expect SPNEGO_E_BUFFER_TOO_SMALL here, 264*613a2f6bSGordon Ross * but if the payload is missing, we'll 265*613a2f6bSGordon Ross * get SPNEGO_E_ELEMENT_UNAVAILABLE. 266*613a2f6bSGordon Ross */ 267*613a2f6bSGordon Ross rc = spnegoGetMechToken(stok_in, NULL, &toklen); 268*613a2f6bSGordon Ross switch (rc) { 269*613a2f6bSGordon Ross case SPNEGO_E_ELEMENT_UNAVAILABLE: 270*613a2f6bSGordon Ross toklen = 0; 271*613a2f6bSGordon Ross break; 272*613a2f6bSGordon Ross case SPNEGO_E_BUFFER_TOO_SMALL: 273*613a2f6bSGordon Ross /* have toklen */ 274*613a2f6bSGordon Ross break; 275*613a2f6bSGordon Ross default: 276*613a2f6bSGordon Ross DPRINT("GetMechTok1, rc %d", rc); 277*613a2f6bSGordon Ross err = EBADRPC; 278*613a2f6bSGordon Ross goto out; 279*613a2f6bSGordon Ross } 280*613a2f6bSGordon Ross err = mb_init(&body_in, (size_t)toklen); 281*613a2f6bSGordon Ross if (err) 282*613a2f6bSGordon Ross goto out; 283*613a2f6bSGordon Ross m = body_in.mb_top; 284*613a2f6bSGordon Ross if (toklen > 0) { 285*613a2f6bSGordon Ross rc = spnegoGetMechToken(stok_in, 286*613a2f6bSGordon Ross (uchar_t *)m->m_data, &toklen); 287*613a2f6bSGordon Ross if (rc) { 288*613a2f6bSGordon Ross DPRINT("GetMechTok2, rc %d", rc); 289*613a2f6bSGordon Ross err = EBADRPC; 290*613a2f6bSGordon Ross goto out; 291*613a2f6bSGordon Ross } 292*613a2f6bSGordon Ross body_in.mb_count = m->m_len = (size_t)toklen; 293*613a2f6bSGordon Ross } 294*613a2f6bSGordon Ross } 295*613a2f6bSGordon Ross 296*613a2f6bSGordon Ross /* 297*613a2f6bSGordon Ross * Call the back-end security provider (SSP) to 298*613a2f6bSGordon Ross * handle the received token (if present) and 299*613a2f6bSGordon Ross * generate an output token (if requested). 300*613a2f6bSGordon Ross */ 301*613a2f6bSGordon Ross err = sp->sp_nexttok(sp, 302*613a2f6bSGordon Ross caller_in ? &body_in : NULL, 303*613a2f6bSGordon Ross caller_out ? &body_out : NULL); 304*613a2f6bSGordon Ross if (err) 305*613a2f6bSGordon Ross goto out; 306*613a2f6bSGordon Ross 307*613a2f6bSGordon Ross /* 308*613a2f6bSGordon Ross * Wrap the outgoing body if requested, 309*613a2f6bSGordon Ross * either negTokenInit on first call, or 310*613a2f6bSGordon Ross * negTokenTarg on subsequent calls. 311*613a2f6bSGordon Ross */ 312*613a2f6bSGordon Ross if (caller_out != NULL) { 313*613a2f6bSGordon Ross m = body_out.mb_top; 314*613a2f6bSGordon Ross 315*613a2f6bSGordon Ross if (caller_in == NULL) { 316*613a2f6bSGordon Ross /* 317*613a2f6bSGordon Ross * This is the first call, so create a 318*613a2f6bSGordon Ross * negTokenInit. 319*613a2f6bSGordon Ross */ 320*613a2f6bSGordon Ross rc = spnegoCreateNegTokenInit( 321*613a2f6bSGordon Ross sp->sp_mech, 0, 322*613a2f6bSGordon Ross (uchar_t *)m->m_data, m->m_len, 323*613a2f6bSGordon Ross NULL, 0, &stok_out); 324*613a2f6bSGordon Ross /* Note: allocated stok_out */ 325*613a2f6bSGordon Ross } else { 326*613a2f6bSGordon Ross /* 327*613a2f6bSGordon Ross * Note: must pass spnego_mech_oid_NotUsed, 328*613a2f6bSGordon Ross * instead of sp->sp_mech so that the spnego 329*613a2f6bSGordon Ross * code will not marshal a mech OID list. 330*613a2f6bSGordon Ross * The mechanism is determined at this point, 331*613a2f6bSGordon Ross * and some servers won't parse an unexpected 332*613a2f6bSGordon Ross * mech. OID list in a negTokenTarg 333*613a2f6bSGordon Ross */ 334*613a2f6bSGordon Ross rc = spnegoCreateNegTokenTarg( 335*613a2f6bSGordon Ross spnego_mech_oid_NotUsed, 336*613a2f6bSGordon Ross spnego_negresult_NotUsed, 337*613a2f6bSGordon Ross (uchar_t *)m->m_data, m->m_len, 338*613a2f6bSGordon Ross NULL, 0, &stok_out); 339*613a2f6bSGordon Ross /* Note: allocated stok_out */ 340*613a2f6bSGordon Ross } 341*613a2f6bSGordon Ross if (rc) { 342*613a2f6bSGordon Ross DPRINT("CreateNegTokenX, rc 0x%x", rc); 343*613a2f6bSGordon Ross err = EBADRPC; 344*613a2f6bSGordon Ross goto out; 345*613a2f6bSGordon Ross } 346*613a2f6bSGordon Ross 347*613a2f6bSGordon Ross /* 348*613a2f6bSGordon Ross * Copy binary from stok_out to caller_out 349*613a2f6bSGordon Ross * Two calls: get the size, get the data. 350*613a2f6bSGordon Ross */ 351*613a2f6bSGordon Ross rc = spnegoTokenGetBinary(stok_out, NULL, &toklen); 352*613a2f6bSGordon Ross if (rc != SPNEGO_E_BUFFER_TOO_SMALL) { 353*613a2f6bSGordon Ross DPRINT("GetBinary1, rc 0x%x", rc); 354*613a2f6bSGordon Ross err = EBADRPC; 355*613a2f6bSGordon Ross goto out; 356*613a2f6bSGordon Ross } 357*613a2f6bSGordon Ross err = mb_init(caller_out, (size_t)toklen); 358*613a2f6bSGordon Ross if (err) 359*613a2f6bSGordon Ross goto out; 360*613a2f6bSGordon Ross m = caller_out->mb_top; 361*613a2f6bSGordon Ross rc = spnegoTokenGetBinary(stok_out, 362*613a2f6bSGordon Ross (uchar_t *)m->m_data, &toklen); 363*613a2f6bSGordon Ross if (rc) { 364*613a2f6bSGordon Ross DPRINT("GetBinary2, rc 0x%x", rc); 365*613a2f6bSGordon Ross err = EBADRPC; 366*613a2f6bSGordon Ross goto out; 367*613a2f6bSGordon Ross } 368*613a2f6bSGordon Ross caller_out->mb_count = m->m_len = (size_t)toklen; 369*613a2f6bSGordon Ross } else { 370*613a2f6bSGordon Ross /* 371*613a2f6bSGordon Ross * caller_out == NULL, so this is the "final" call. 372*613a2f6bSGordon Ross * Get final SPNEGO result from the INPUT token. 373*613a2f6bSGordon Ross */ 374*613a2f6bSGordon Ross rc = spnegoGetNegotiationResult(stok_in, &result); 375*613a2f6bSGordon Ross if (rc) { 376*613a2f6bSGordon Ross DPRINT("rc 0x%x", rc); 377*613a2f6bSGordon Ross err = EBADRPC; 378*613a2f6bSGordon Ross goto out; 379*613a2f6bSGordon Ross } 380*613a2f6bSGordon Ross DPRINT("spnego result: 0x%x", result); 381*613a2f6bSGordon Ross if (result != spnego_negresult_success) { 382*613a2f6bSGordon Ross err = EAUTH; 383*613a2f6bSGordon Ross goto out; 384*613a2f6bSGordon Ross } 385*613a2f6bSGordon Ross } 386*613a2f6bSGordon Ross err = 0; 387*613a2f6bSGordon Ross 388*613a2f6bSGordon Ross out: 389*613a2f6bSGordon Ross mb_done(&body_in); 390*613a2f6bSGordon Ross mb_done(&body_out); 391*613a2f6bSGordon Ross spnegoFreeData(stok_in); 392*613a2f6bSGordon Ross spnegoFreeData(stok_out); 393*613a2f6bSGordon Ross 394*613a2f6bSGordon Ross return (err); 395*613a2f6bSGordon Ross } 396