michael@0: /* vim:set ts=4 sw=4 sts=4 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // michael@0: // GSSAPI Authentication Support Module michael@0: // michael@0: // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt michael@0: // (formerly draft-brezak-spnego-http-04.txt) michael@0: // michael@0: // Also described here: michael@0: // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp michael@0: // michael@0: // michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include "prlink.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsNativeCharsetUtils.h" michael@0: #include "mozilla/Telemetry.h" michael@0: michael@0: #include "nsAuthGSSAPI.h" michael@0: michael@0: #ifdef XP_MACOSX michael@0: #include michael@0: #endif michael@0: michael@0: #ifdef XP_MACOSX michael@0: typedef KLStatus (*KLCacheHasValidTickets_type)( michael@0: KLPrincipal, michael@0: KLKerberosVersion, michael@0: KLBoolean *, michael@0: KLPrincipal *, michael@0: char **); michael@0: #endif michael@0: michael@0: #if defined(HAVE_RES_NINIT) michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: // We define GSS_C_NT_HOSTBASED_SERVICE explicitly since it may be referenced michael@0: // by by a different name depending on the implementation of gss but always michael@0: // has the same value michael@0: michael@0: static gss_OID_desc gss_c_nt_hostbased_service = michael@0: { 10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04" }; michael@0: michael@0: static const char kNegotiateAuthGssLib[] = michael@0: "network.negotiate-auth.gsslib"; michael@0: static const char kNegotiateAuthNativeImp[] = michael@0: "network.negotiate-auth.using-native-gsslib"; michael@0: michael@0: static struct GSSFunction { michael@0: const char *str; michael@0: PRFuncPtr func; michael@0: } gssFuncs[] = { michael@0: { "gss_display_status", nullptr }, michael@0: { "gss_init_sec_context", nullptr }, michael@0: { "gss_indicate_mechs", nullptr }, michael@0: { "gss_release_oid_set", nullptr }, michael@0: { "gss_delete_sec_context", nullptr }, michael@0: { "gss_import_name", nullptr }, michael@0: { "gss_release_buffer", nullptr }, michael@0: { "gss_release_name", nullptr }, michael@0: { "gss_wrap", nullptr }, michael@0: { "gss_unwrap", nullptr } michael@0: }; michael@0: michael@0: static bool gssNativeImp = true; michael@0: static PRLibrary* gssLibrary = nullptr; michael@0: michael@0: #define gss_display_status_ptr ((gss_display_status_type)*gssFuncs[0].func) michael@0: #define gss_init_sec_context_ptr ((gss_init_sec_context_type)*gssFuncs[1].func) michael@0: #define gss_indicate_mechs_ptr ((gss_indicate_mechs_type)*gssFuncs[2].func) michael@0: #define gss_release_oid_set_ptr ((gss_release_oid_set_type)*gssFuncs[3].func) michael@0: #define gss_delete_sec_context_ptr ((gss_delete_sec_context_type)*gssFuncs[4].func) michael@0: #define gss_import_name_ptr ((gss_import_name_type)*gssFuncs[5].func) michael@0: #define gss_release_buffer_ptr ((gss_release_buffer_type)*gssFuncs[6].func) michael@0: #define gss_release_name_ptr ((gss_release_name_type)*gssFuncs[7].func) michael@0: #define gss_wrap_ptr ((gss_wrap_type)*gssFuncs[8].func) michael@0: #define gss_unwrap_ptr ((gss_unwrap_type)*gssFuncs[9].func) michael@0: michael@0: #ifdef XP_MACOSX michael@0: static PRFuncPtr KLCacheHasValidTicketsPtr; michael@0: #define KLCacheHasValidTickets_ptr \ michael@0: ((KLCacheHasValidTickets_type)*KLCacheHasValidTicketsPtr) michael@0: #endif michael@0: michael@0: static nsresult michael@0: gssInit() michael@0: { michael@0: nsXPIDLCString libPath; michael@0: nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: if (prefs) { michael@0: prefs->GetCharPref(kNegotiateAuthGssLib, getter_Copies(libPath)); michael@0: prefs->GetBoolPref(kNegotiateAuthNativeImp, &gssNativeImp); michael@0: } michael@0: michael@0: PRLibrary *lib = nullptr; michael@0: michael@0: if (!libPath.IsEmpty()) { michael@0: LOG(("Attempting to load user specified library [%s]\n", libPath.get())); michael@0: gssNativeImp = false; michael@0: lib = PR_LoadLibrary(libPath.get()); michael@0: } michael@0: else { michael@0: #ifdef XP_WIN michael@0: char *libName = PR_GetLibraryName(nullptr, "gssapi32"); michael@0: if (libName) { michael@0: lib = PR_LoadLibrary("gssapi32"); michael@0: PR_FreeLibraryName(libName); michael@0: } michael@0: #elif defined(__OpenBSD__) michael@0: /* OpenBSD doesn't register inter-library dependencies in basesystem michael@0: * libs therefor we need to load all the libraries gssapi depends on, michael@0: * in the correct order and with LD_GLOBAL for GSSAPI auth to work michael@0: * fine. michael@0: */ michael@0: michael@0: const char *const verLibNames[] = { michael@0: "libasn1.so", michael@0: "libcrypto.so", michael@0: "libroken.so", michael@0: "libheimbase.so", michael@0: "libcom_err.so", michael@0: "libkrb5.so", michael@0: "libgssapi.so" michael@0: }; michael@0: michael@0: PRLibSpec libSpec; michael@0: for (size_t i = 0; i < ArrayLength(verLibNames); ++i) { michael@0: libSpec.type = PR_LibSpec_Pathname; michael@0: libSpec.value.pathname = verLibNames[i]; michael@0: lib = PR_LoadLibraryWithFlags(libSpec, PR_LD_GLOBAL); michael@0: }; michael@0: michael@0: #else michael@0: michael@0: const char *const libNames[] = { michael@0: "gss", michael@0: "gssapi_krb5", michael@0: "gssapi" michael@0: }; michael@0: michael@0: const char *const verLibNames[] = { michael@0: "libgssapi_krb5.so.2", /* MIT - FC, Suse10, Debian */ michael@0: "libgssapi.so.4", /* Heimdal - Suse10, MDK */ michael@0: "libgssapi.so.1" /* Heimdal - Suse9, CITI - FC, MDK, Suse10*/ michael@0: }; michael@0: michael@0: for (size_t i = 0; i < ArrayLength(verLibNames) && !lib; ++i) { michael@0: lib = PR_LoadLibrary(verLibNames[i]); michael@0: michael@0: /* The CITI libgssapi library calls exit() during michael@0: * initialization if it's not correctly configured. Try to michael@0: * ensure that we never use this library for our GSSAPI michael@0: * support, as its just a wrapper library, anyway. michael@0: * See Bugzilla #325433 michael@0: */ michael@0: if (lib && michael@0: PR_FindFunctionSymbol(lib, michael@0: "internal_krb5_gss_initialize") && michael@0: PR_FindFunctionSymbol(lib, "gssd_pname_to_uid")) { michael@0: LOG(("CITI libgssapi found, which calls exit(). Skipping\n")); michael@0: PR_UnloadLibrary(lib); michael@0: lib = nullptr; michael@0: } michael@0: } michael@0: michael@0: for (size_t i = 0; i < ArrayLength(libNames) && !lib; ++i) { michael@0: char *libName = PR_GetLibraryName(nullptr, libNames[i]); michael@0: if (libName) { michael@0: lib = PR_LoadLibrary(libName); michael@0: PR_FreeLibraryName(libName); michael@0: michael@0: if (lib && michael@0: PR_FindFunctionSymbol(lib, michael@0: "internal_krb5_gss_initialize") && michael@0: PR_FindFunctionSymbol(lib, "gssd_pname_to_uid")) { michael@0: LOG(("CITI libgssapi found, which calls exit(). Skipping\n")); michael@0: PR_UnloadLibrary(lib); michael@0: lib = nullptr; michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: if (!lib) { michael@0: LOG(("Fail to load gssapi library\n")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: LOG(("Attempting to load gss functions\n")); michael@0: michael@0: for (size_t i = 0; i < ArrayLength(gssFuncs); ++i) { michael@0: gssFuncs[i].func = PR_FindFunctionSymbol(lib, gssFuncs[i].str); michael@0: if (!gssFuncs[i].func) { michael@0: LOG(("Fail to load %s function from gssapi library\n", gssFuncs[i].str)); michael@0: PR_UnloadLibrary(lib); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: #ifdef XP_MACOSX michael@0: if (gssNativeImp && michael@0: !(KLCacheHasValidTicketsPtr = michael@0: PR_FindFunctionSymbol(lib, "KLCacheHasValidTickets"))) { michael@0: LOG(("Fail to load KLCacheHasValidTickets function from gssapi library\n")); michael@0: PR_UnloadLibrary(lib); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: #endif michael@0: michael@0: gssLibrary = lib; michael@0: return NS_OK; michael@0: } michael@0: michael@0: #if defined( PR_LOGGING ) michael@0: michael@0: // Generate proper GSSAPI error messages from the major and michael@0: // minor status codes. michael@0: void michael@0: LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, const char *prefix) michael@0: { michael@0: OM_uint32 new_stat; michael@0: OM_uint32 msg_ctx = 0; michael@0: gss_buffer_desc status1_string; michael@0: gss_buffer_desc status2_string; michael@0: OM_uint32 ret; michael@0: nsAutoCString errorStr; michael@0: errorStr.Assign(prefix); michael@0: michael@0: if (!gssLibrary) michael@0: return; michael@0: michael@0: errorStr += ": "; michael@0: do { michael@0: ret = gss_display_status_ptr(&new_stat, michael@0: maj_stat, michael@0: GSS_C_GSS_CODE, michael@0: GSS_C_NULL_OID, michael@0: &msg_ctx, michael@0: &status1_string); michael@0: errorStr.Append((const char *) status1_string.value, status1_string.length); michael@0: gss_release_buffer_ptr(&new_stat, &status1_string); michael@0: michael@0: errorStr += '\n'; michael@0: ret = gss_display_status_ptr(&new_stat, michael@0: min_stat, michael@0: GSS_C_MECH_CODE, michael@0: GSS_C_NULL_OID, michael@0: &msg_ctx, michael@0: &status2_string); michael@0: errorStr.Append((const char *) status2_string.value, status2_string.length); michael@0: errorStr += '\n'; michael@0: } while (!GSS_ERROR(ret) && msg_ctx != 0); michael@0: michael@0: LOG(("%s\n", errorStr.get())); michael@0: } michael@0: michael@0: #else /* PR_LOGGING */ michael@0: michael@0: #define LogGssError(x,y,z) michael@0: michael@0: #endif /* PR_LOGGING */ michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsAuthGSSAPI::nsAuthGSSAPI(pType package) michael@0: : mServiceFlags(REQ_DEFAULT) michael@0: { michael@0: OM_uint32 minstat; michael@0: OM_uint32 majstat; michael@0: gss_OID_set mech_set; michael@0: gss_OID item; michael@0: michael@0: unsigned int i; michael@0: static gss_OID_desc gss_krb5_mech_oid_desc = michael@0: { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; michael@0: static gss_OID_desc gss_spnego_mech_oid_desc = michael@0: { 6, (void *) "\x2b\x06\x01\x05\x05\x02" }; michael@0: michael@0: LOG(("entering nsAuthGSSAPI::nsAuthGSSAPI()\n")); michael@0: michael@0: mComplete = false; michael@0: michael@0: if (!gssLibrary && NS_FAILED(gssInit())) michael@0: return; michael@0: michael@0: mCtx = GSS_C_NO_CONTEXT; michael@0: mMechOID = &gss_krb5_mech_oid_desc; michael@0: michael@0: // if the type is kerberos we accept it as default michael@0: // and exit michael@0: michael@0: if (package == PACKAGE_TYPE_KERBEROS) michael@0: return; michael@0: michael@0: // Now, look at the list of supported mechanisms, michael@0: // if SPNEGO is found, then use it. michael@0: // Otherwise, set the desired mechanism to michael@0: // GSS_C_NO_OID and let the system try to use michael@0: // the default mechanism. michael@0: // michael@0: // Using Kerberos directly (instead of negotiating michael@0: // with SPNEGO) may work in some cases depending michael@0: // on how smart the server side is. michael@0: michael@0: majstat = gss_indicate_mechs_ptr(&minstat, &mech_set); michael@0: if (GSS_ERROR(majstat)) michael@0: return; michael@0: michael@0: if (mech_set) { michael@0: for (i=0; icount; i++) { michael@0: item = &mech_set->elements[i]; michael@0: if (item->length == gss_spnego_mech_oid_desc.length && michael@0: !memcmp(item->elements, gss_spnego_mech_oid_desc.elements, michael@0: item->length)) { michael@0: // ok, we found it michael@0: mMechOID = &gss_spnego_mech_oid_desc; michael@0: break; michael@0: } michael@0: } michael@0: gss_release_oid_set_ptr(&minstat, &mech_set); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsAuthGSSAPI::Reset() michael@0: { michael@0: if (gssLibrary && mCtx != GSS_C_NO_CONTEXT) { michael@0: OM_uint32 minor_status; michael@0: gss_delete_sec_context_ptr(&minor_status, &mCtx, GSS_C_NO_BUFFER); michael@0: } michael@0: mCtx = GSS_C_NO_CONTEXT; michael@0: mComplete = false; michael@0: } michael@0: michael@0: /* static */ void michael@0: nsAuthGSSAPI::Shutdown() michael@0: { michael@0: if (gssLibrary) { michael@0: PR_UnloadLibrary(gssLibrary); michael@0: gssLibrary = nullptr; michael@0: } michael@0: } michael@0: michael@0: /* Limitations apply to this class's thread safety. See the header file */ michael@0: NS_IMPL_ISUPPORTS(nsAuthGSSAPI, nsIAuthModule) michael@0: michael@0: NS_IMETHODIMP michael@0: nsAuthGSSAPI::Init(const char *serviceName, michael@0: uint32_t serviceFlags, michael@0: const char16_t *domain, michael@0: const char16_t *username, michael@0: const char16_t *password) michael@0: { michael@0: // we don't expect to be passed any user credentials michael@0: NS_ASSERTION(!domain && !username && !password, "unexpected credentials"); michael@0: michael@0: // it's critial that the caller supply a service name to be used michael@0: NS_ENSURE_TRUE(serviceName && *serviceName, NS_ERROR_INVALID_ARG); michael@0: michael@0: LOG(("entering nsAuthGSSAPI::Init()\n")); michael@0: michael@0: if (!gssLibrary) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: mServiceName = serviceName; michael@0: mServiceFlags = serviceFlags; michael@0: michael@0: static bool sTelemetrySent = false; michael@0: if (!sTelemetrySent) { michael@0: mozilla::Telemetry::Accumulate( michael@0: mozilla::Telemetry::NTLM_MODULE_USED_2, michael@0: serviceFlags & nsIAuthModule::REQ_PROXY_AUTH michael@0: ? NTLM_MODULE_KERBEROS_PROXY michael@0: : NTLM_MODULE_KERBEROS_DIRECT); michael@0: sTelemetrySent = true; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAuthGSSAPI::GetNextToken(const void *inToken, michael@0: uint32_t inTokenLen, michael@0: void **outToken, michael@0: uint32_t *outTokenLen) michael@0: { michael@0: OM_uint32 major_status, minor_status; michael@0: OM_uint32 req_flags = 0; michael@0: gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; michael@0: gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; michael@0: gss_buffer_t in_token_ptr = GSS_C_NO_BUFFER; michael@0: gss_name_t server; michael@0: nsAutoCString userbuf; michael@0: nsresult rv; michael@0: michael@0: LOG(("entering nsAuthGSSAPI::GetNextToken()\n")); michael@0: michael@0: if (!gssLibrary) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: // If they've called us again after we're complete, reset to start afresh. michael@0: if (mComplete) michael@0: Reset(); michael@0: michael@0: if (mServiceFlags & REQ_DELEGATE) michael@0: req_flags |= GSS_C_DELEG_FLAG; michael@0: michael@0: if (mServiceFlags & REQ_MUTUAL_AUTH) michael@0: req_flags |= GSS_C_MUTUAL_FLAG; michael@0: michael@0: input_token.value = (void *)mServiceName.get(); michael@0: input_token.length = mServiceName.Length() + 1; michael@0: michael@0: #if defined(HAVE_RES_NINIT) michael@0: res_ninit(&_res); michael@0: #endif michael@0: major_status = gss_import_name_ptr(&minor_status, michael@0: &input_token, michael@0: &gss_c_nt_hostbased_service, michael@0: &server); michael@0: input_token.value = nullptr; michael@0: input_token.length = 0; michael@0: if (GSS_ERROR(major_status)) { michael@0: LogGssError(major_status, minor_status, "gss_import_name() failed"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (inToken) { michael@0: input_token.length = inTokenLen; michael@0: input_token.value = (void *) inToken; michael@0: in_token_ptr = &input_token; michael@0: } michael@0: else if (mCtx != GSS_C_NO_CONTEXT) { michael@0: // If there is no input token, then we are starting a new michael@0: // authentication sequence. If we have already initialized our michael@0: // security context, then we're in trouble because it means that the michael@0: // first sequence failed. We need to bail or else we might end up in michael@0: // an infinite loop. michael@0: LOG(("Cannot restart authentication sequence!")); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: #if defined(XP_MACOSX) michael@0: // Suppress Kerberos prompts to get credentials. See bug 240643. michael@0: // We can only use Mac OS X specific kerb functions if we are using michael@0: // the native lib michael@0: KLBoolean found; michael@0: bool doingMailTask = mServiceName.Find("imap@") || michael@0: mServiceName.Find("pop@") || michael@0: mServiceName.Find("smtp@") || michael@0: mServiceName.Find("ldap@"); michael@0: michael@0: if (!doingMailTask && (gssNativeImp && michael@0: (KLCacheHasValidTickets_ptr(nullptr, kerberosVersion_V5, &found, nullptr, nullptr) != klNoErr || !found))) michael@0: { michael@0: major_status = GSS_S_FAILURE; michael@0: minor_status = 0; michael@0: } michael@0: else michael@0: #endif /* XP_MACOSX */ michael@0: major_status = gss_init_sec_context_ptr(&minor_status, michael@0: GSS_C_NO_CREDENTIAL, michael@0: &mCtx, michael@0: server, michael@0: mMechOID, michael@0: req_flags, michael@0: GSS_C_INDEFINITE, michael@0: GSS_C_NO_CHANNEL_BINDINGS, michael@0: in_token_ptr, michael@0: nullptr, michael@0: &output_token, michael@0: nullptr, michael@0: nullptr); michael@0: michael@0: if (GSS_ERROR(major_status)) { michael@0: LogGssError(major_status, minor_status, "gss_init_sec_context() failed"); michael@0: Reset(); michael@0: rv = NS_ERROR_FAILURE; michael@0: goto end; michael@0: } michael@0: if (major_status == GSS_S_COMPLETE) { michael@0: // Mark ourselves as being complete, so that if we're called again michael@0: // we know to start afresh. michael@0: mComplete = true; michael@0: } michael@0: else if (major_status == GSS_S_CONTINUE_NEEDED) { michael@0: // michael@0: // The important thing is that we do NOT reset the michael@0: // context here because it will be needed on the michael@0: // next call. michael@0: // michael@0: } michael@0: michael@0: *outTokenLen = output_token.length; michael@0: if (output_token.length != 0) michael@0: *outToken = nsMemory::Clone(output_token.value, output_token.length); michael@0: else michael@0: *outToken = nullptr; michael@0: michael@0: gss_release_buffer_ptr(&minor_status, &output_token); michael@0: michael@0: if (major_status == GSS_S_COMPLETE) michael@0: rv = NS_SUCCESS_AUTH_FINISHED; michael@0: else michael@0: rv = NS_OK; michael@0: michael@0: end: michael@0: gss_release_name_ptr(&minor_status, &server); michael@0: michael@0: LOG((" leaving nsAuthGSSAPI::GetNextToken [rv=%x]", rv)); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAuthGSSAPI::Unwrap(const void *inToken, michael@0: uint32_t inTokenLen, michael@0: void **outToken, michael@0: uint32_t *outTokenLen) michael@0: { michael@0: OM_uint32 major_status, minor_status; michael@0: michael@0: gss_buffer_desc input_token; michael@0: gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; michael@0: michael@0: input_token.value = (void *) inToken; michael@0: input_token.length = inTokenLen; michael@0: michael@0: major_status = gss_unwrap_ptr(&minor_status, michael@0: mCtx, michael@0: &input_token, michael@0: &output_token, michael@0: nullptr, michael@0: nullptr); michael@0: if (GSS_ERROR(major_status)) { michael@0: LogGssError(major_status, minor_status, "gss_unwrap() failed"); michael@0: Reset(); michael@0: gss_release_buffer_ptr(&minor_status, &output_token); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: *outTokenLen = output_token.length; michael@0: michael@0: if (output_token.length) michael@0: *outToken = nsMemory::Clone(output_token.value, output_token.length); michael@0: else michael@0: *outToken = nullptr; michael@0: michael@0: gss_release_buffer_ptr(&minor_status, &output_token); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAuthGSSAPI::Wrap(const void *inToken, michael@0: uint32_t inTokenLen, michael@0: bool confidential, michael@0: void **outToken, michael@0: uint32_t *outTokenLen) michael@0: { michael@0: OM_uint32 major_status, minor_status; michael@0: michael@0: gss_buffer_desc input_token; michael@0: gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; michael@0: michael@0: input_token.value = (void *) inToken; michael@0: input_token.length = inTokenLen; michael@0: michael@0: major_status = gss_wrap_ptr(&minor_status, michael@0: mCtx, michael@0: confidential, michael@0: GSS_C_QOP_DEFAULT, michael@0: &input_token, michael@0: nullptr, michael@0: &output_token); michael@0: michael@0: if (GSS_ERROR(major_status)) { michael@0: LogGssError(major_status, minor_status, "gss_wrap() failed"); michael@0: Reset(); michael@0: gss_release_buffer_ptr(&minor_status, &output_token); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: *outTokenLen = output_token.length; michael@0: michael@0: /* it is not possible for output_token.length to be zero */ michael@0: *outToken = nsMemory::Clone(output_token.value, output_token.length); michael@0: gss_release_buffer_ptr(&minor_status, &output_token); michael@0: michael@0: return NS_OK; michael@0: } michael@0: