tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsAuthGSSAPI.cpp (18543B)


      1 /* vim:set ts=4 sw=2 sts=2 et cindent: */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 //
      7 // GSSAPI Authentication Support Module
      8 //
      9 // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt
     10 // (formerly draft-brezak-spnego-http-04.txt)
     11 //
     12 // Also described here:
     13 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp
     14 //
     15 //
     16 
     17 #include "mozilla/IntegerPrintfMacros.h"
     18 
     19 #include "nsCOMPtr.h"
     20 #include "nsNativeCharsetUtils.h"
     21 #include "mozilla/Preferences.h"
     22 #include "mozilla/SharedLibrary.h"
     23 #include "mozilla/glean/SecurityManagerSslMetrics.h"
     24 
     25 #include "nsAuthGSSAPI.h"
     26 
     27 #ifdef XP_MACOSX
     28 #  include <Kerberos/Kerberos.h>
     29 #endif
     30 
     31 #ifdef XP_MACOSX
     32 typedef KLStatus (*KLCacheHasValidTickets_type)(KLPrincipal, KLKerberosVersion,
     33                                                KLBoolean*, KLPrincipal*,
     34                                                char**);
     35 #endif
     36 
     37 #if defined(HAVE_RES_NINIT)
     38 #  include <sys/types.h>
     39 #  include <netinet/in.h>
     40 #  include <arpa/nameser.h>
     41 #  include <resolv.h>
     42 #endif
     43 
     44 using namespace mozilla;
     45 
     46 //-----------------------------------------------------------------------------
     47 
     48 // We define GSS_C_NT_HOSTBASED_SERVICE explicitly since it may be referenced
     49 // by by a different name depending on the implementation of gss but always
     50 // has the same value
     51 
     52 static gss_OID_desc gss_c_nt_hostbased_service = {
     53    10, (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"};
     54 
     55 static const char kNegotiateAuthGssLib[] = "network.negotiate-auth.gsslib";
     56 static const char kNegotiateAuthNativeImp[] =
     57    "network.negotiate-auth.using-native-gsslib";
     58 
     59 static struct GSSFunction {
     60  const char* str;
     61  PRFuncPtr func;
     62 } gssFuncs[] = {{"gss_display_status", nullptr},
     63                {"gss_init_sec_context", nullptr},
     64                {"gss_indicate_mechs", nullptr},
     65                {"gss_release_oid_set", nullptr},
     66                {"gss_delete_sec_context", nullptr},
     67                {"gss_import_name", nullptr},
     68                {"gss_release_buffer", nullptr},
     69                {"gss_release_name", nullptr},
     70                {"gss_wrap", nullptr},
     71                {"gss_unwrap", nullptr}};
     72 
     73 static bool gssNativeImp = true;
     74 static PRLibrary* gssLibrary = nullptr;
     75 
     76 #define gss_display_status_ptr ((gss_display_status_type) * gssFuncs[0].func)
     77 #define gss_init_sec_context_ptr \
     78  ((gss_init_sec_context_type) * gssFuncs[1].func)
     79 #define gss_indicate_mechs_ptr ((gss_indicate_mechs_type) * gssFuncs[2].func)
     80 #define gss_release_oid_set_ptr ((gss_release_oid_set_type) * gssFuncs[3].func)
     81 #define gss_delete_sec_context_ptr \
     82  ((gss_delete_sec_context_type) * gssFuncs[4].func)
     83 #define gss_import_name_ptr ((gss_import_name_type) * gssFuncs[5].func)
     84 #define gss_release_buffer_ptr ((gss_release_buffer_type) * gssFuncs[6].func)
     85 #define gss_release_name_ptr ((gss_release_name_type) * gssFuncs[7].func)
     86 #define gss_wrap_ptr ((gss_wrap_type) * gssFuncs[8].func)
     87 #define gss_unwrap_ptr ((gss_unwrap_type) * gssFuncs[9].func)
     88 
     89 #ifdef XP_MACOSX
     90 static PRFuncPtr KLCacheHasValidTicketsPtr;
     91 #  define KLCacheHasValidTickets_ptr \
     92    ((KLCacheHasValidTickets_type) * KLCacheHasValidTicketsPtr)
     93 #endif
     94 
     95 static nsresult gssInit() {
     96 #ifdef XP_WIN
     97  nsAutoString libPathU;
     98  Preferences::GetString(kNegotiateAuthGssLib, libPathU);
     99  NS_ConvertUTF16toUTF8 libPath(libPathU);
    100 #else
    101  nsAutoCString libPath;
    102  Preferences::GetCString(kNegotiateAuthGssLib, libPath);
    103 #endif
    104  gssNativeImp = Preferences::GetBool(kNegotiateAuthNativeImp);
    105 
    106  PRLibrary* lib = nullptr;
    107 
    108  if (!libPath.IsEmpty()) {
    109    LOG(("Attempting to load user specified library [%s]\n", libPath.get()));
    110    gssNativeImp = false;
    111 #ifdef XP_WIN
    112    lib = LoadLibraryWithFlags(libPathU.get());
    113 #else
    114    lib = LoadLibraryWithFlags(libPath.get());
    115 #endif
    116  } else {
    117 #ifdef XP_WIN
    118 #  ifdef _WIN64
    119    constexpr auto kLibName = u"gssapi64.dll"_ns;
    120 #  else
    121    constexpr auto kLibName = u"gssapi32.dll"_ns;
    122 #  endif
    123 
    124    lib = LoadLibraryWithFlags(kLibName.get());
    125 #elif defined(__OpenBSD__)
    126    /* OpenBSD doesn't register inter-library dependencies in basesystem
    127     * libs therefor we need to load all the libraries gssapi depends on,
    128     * in the correct order and with LD_GLOBAL for GSSAPI auth to work
    129     * fine.
    130     */
    131 
    132    const char* const verLibNames[] = {
    133        "libasn1.so",    "libcrypto.so", "libroken.so", "libheimbase.so",
    134        "libcom_err.so", "libkrb5.so",   "libgssapi.so"};
    135 
    136    PRLibSpec libSpec;
    137    for (size_t i = 0; i < std::size(verLibNames); ++i) {
    138      libSpec.type = PR_LibSpec_Pathname;
    139      libSpec.value.pathname = verLibNames[i];
    140      lib = PR_LoadLibraryWithFlags(libSpec, PR_LD_GLOBAL);
    141    }
    142 
    143 #else
    144 
    145    const char* const libNames[] = {"gss", "gssapi_krb5", "gssapi"};
    146 
    147    const char* const verLibNames[] = {
    148        "libgssapi_krb5.so.2", /* MIT - FC, Suse10, Debian */
    149        "libgssapi.so.4",      /* Heimdal - Suse10, MDK */
    150        "libgssapi.so.1"       /* Heimdal - Suse9, CITI - FC, MDK, Suse10*/
    151    };
    152 
    153    for (size_t i = 0; i < std::size(verLibNames) && !lib; ++i) {
    154      lib = PR_LoadLibrary(verLibNames[i]);
    155 
    156      /* The CITI libgssapi library calls exit() during
    157       * initialization if it's not correctly configured. Try to
    158       * ensure that we never use this library for our GSSAPI
    159       * support, as its just a wrapper library, anyway.
    160       * See Bugzilla #325433
    161       */
    162      if (lib && PR_FindFunctionSymbol(lib, "internal_krb5_gss_initialize") &&
    163          PR_FindFunctionSymbol(lib, "gssd_pname_to_uid")) {
    164        LOG(("CITI libgssapi found, which calls exit(). Skipping\n"));
    165        PR_UnloadLibrary(lib);
    166        lib = nullptr;
    167      }
    168    }
    169 
    170    for (size_t i = 0; i < std::size(libNames) && !lib; ++i) {
    171      char* libName = PR_GetLibraryName(nullptr, libNames[i]);
    172      if (libName) {
    173        lib = PR_LoadLibrary(libName);
    174        PR_FreeLibraryName(libName);
    175 
    176        if (lib && PR_FindFunctionSymbol(lib, "internal_krb5_gss_initialize") &&
    177            PR_FindFunctionSymbol(lib, "gssd_pname_to_uid")) {
    178          LOG(("CITI libgssapi found, which calls exit(). Skipping\n"));
    179          PR_UnloadLibrary(lib);
    180          lib = nullptr;
    181        }
    182      }
    183    }
    184 #endif
    185  }
    186 
    187  if (!lib) {
    188    LOG(("Fail to load gssapi library\n"));
    189    return NS_ERROR_FAILURE;
    190  }
    191 
    192  LOG(("Attempting to load gss functions\n"));
    193 
    194  for (auto& gssFunc : gssFuncs) {
    195    gssFunc.func = PR_FindFunctionSymbol(lib, gssFunc.str);
    196    if (!gssFunc.func) {
    197      LOG(("Fail to load %s function from gssapi library\n", gssFunc.str));
    198      PR_UnloadLibrary(lib);
    199      return NS_ERROR_FAILURE;
    200    }
    201  }
    202 #ifdef XP_MACOSX
    203  if (gssNativeImp && !(KLCacheHasValidTicketsPtr = PR_FindFunctionSymbol(
    204                            lib, "KLCacheHasValidTickets"))) {
    205    LOG(("Fail to load KLCacheHasValidTickets function from gssapi library\n"));
    206    PR_UnloadLibrary(lib);
    207    return NS_ERROR_FAILURE;
    208  }
    209 #endif
    210 
    211  gssLibrary = lib;
    212  return NS_OK;
    213 }
    214 
    215 // Generate proper GSSAPI error messages from the major and
    216 // minor status codes.
    217 void LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, const char* prefix) {
    218  if (!MOZ_LOG_TEST(gNegotiateLog, LogLevel::Debug)) {
    219    return;
    220  }
    221 
    222  OM_uint32 new_stat;
    223  OM_uint32 msg_ctx = 0;
    224  gss_buffer_desc status1_string;
    225  gss_buffer_desc status2_string;
    226  OM_uint32 ret;
    227  nsAutoCString errorStr;
    228  errorStr.Assign(prefix);
    229 
    230  if (!gssLibrary) return;
    231 
    232  errorStr += ": ";
    233  do {
    234    ret = gss_display_status_ptr(&new_stat, maj_stat, GSS_C_GSS_CODE,
    235                                 GSS_C_NULL_OID, &msg_ctx, &status1_string);
    236    errorStr.Append((const char*)status1_string.value, status1_string.length);
    237    gss_release_buffer_ptr(&new_stat, &status1_string);
    238 
    239    errorStr += '\n';
    240    ret = gss_display_status_ptr(&new_stat, min_stat, GSS_C_MECH_CODE,
    241                                 GSS_C_NULL_OID, &msg_ctx, &status2_string);
    242    errorStr.Append((const char*)status2_string.value, status2_string.length);
    243    errorStr += '\n';
    244  } while (!GSS_ERROR(ret) && msg_ctx != 0);
    245 
    246  LOG(("%s\n", errorStr.get()));
    247 }
    248 
    249 //-----------------------------------------------------------------------------
    250 
    251 nsAuthGSSAPI::nsAuthGSSAPI(pType package) : mServiceFlags(REQ_DEFAULT) {
    252  OM_uint32 minstat;
    253  OM_uint32 majstat;
    254  gss_OID_set mech_set;
    255  gss_OID item;
    256 
    257  unsigned int i;
    258  static gss_OID_desc gss_krb5_mech_oid_desc = {
    259      9, (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
    260  static gss_OID_desc gss_spnego_mech_oid_desc = {
    261      6, (void*)"\x2b\x06\x01\x05\x05\x02"};
    262 
    263  LOG(("entering nsAuthGSSAPI::nsAuthGSSAPI()\n"));
    264 
    265  if (!gssLibrary && NS_FAILED(gssInit())) return;
    266 
    267  mCtx = GSS_C_NO_CONTEXT;
    268  mMechOID = &gss_krb5_mech_oid_desc;
    269 
    270  // if the type is kerberos we accept it as default
    271  // and exit
    272 
    273  if (package == PACKAGE_TYPE_KERBEROS) return;
    274 
    275  // Now, look at the list of supported mechanisms,
    276  // if SPNEGO is found, then use it.
    277  // Otherwise, set the desired mechanism to
    278  // GSS_C_NO_OID and let the system try to use
    279  // the default mechanism.
    280  //
    281  // Using Kerberos directly (instead of negotiating
    282  // with SPNEGO) may work in some cases depending
    283  // on how smart the server side is.
    284 
    285  majstat = gss_indicate_mechs_ptr(&minstat, &mech_set);
    286  if (GSS_ERROR(majstat)) return;
    287 
    288  if (mech_set) {
    289    for (i = 0; i < mech_set->count; i++) {
    290      item = &mech_set->elements[i];
    291      if (item->length == gss_spnego_mech_oid_desc.length &&
    292          !memcmp(item->elements, gss_spnego_mech_oid_desc.elements,
    293                  item->length)) {
    294        // ok, we found it
    295        mMechOID = &gss_spnego_mech_oid_desc;
    296        break;
    297      }
    298    }
    299    gss_release_oid_set_ptr(&minstat, &mech_set);
    300  }
    301 }
    302 
    303 void nsAuthGSSAPI::Reset() {
    304  if (gssLibrary && mCtx != GSS_C_NO_CONTEXT) {
    305    OM_uint32 minor_status;
    306    gss_delete_sec_context_ptr(&minor_status, &mCtx, GSS_C_NO_BUFFER);
    307  }
    308  mCtx = GSS_C_NO_CONTEXT;
    309  mComplete = false;
    310  mDelegationRequested = false;
    311  mDelegationSupported = false;
    312 }
    313 
    314 /* static */
    315 void nsAuthGSSAPI::Shutdown() {
    316  if (gssLibrary) {
    317    PR_UnloadLibrary(gssLibrary);
    318    gssLibrary = nullptr;
    319  }
    320 }
    321 
    322 /* Limitations apply to this class's thread safety. See the header file */
    323 NS_IMPL_ISUPPORTS(nsAuthGSSAPI, nsIAuthModule)
    324 
    325 NS_IMETHODIMP
    326 nsAuthGSSAPI::Init(const nsACString& serviceName, uint32_t serviceFlags,
    327                   const nsAString& domain, const nsAString& username,
    328                   const nsAString& password) {
    329  // we don't expect to be passed any user credentials
    330  NS_ASSERTION(domain.IsEmpty() && username.IsEmpty() && password.IsEmpty(),
    331               "unexpected credentials");
    332 
    333  // it's critial that the caller supply a service name to be used
    334  NS_ENSURE_TRUE(!serviceName.IsEmpty(), NS_ERROR_INVALID_ARG);
    335 
    336  LOG(("entering nsAuthGSSAPI::Init()\n"));
    337 
    338  if (!gssLibrary) return NS_ERROR_NOT_INITIALIZED;
    339 
    340  mServiceName = serviceName;
    341  mServiceFlags = serviceFlags;
    342 
    343  static bool sTelemetrySent = false;
    344  if (!sTelemetrySent) {
    345    mozilla::glean::security::ntlm_module_used.AccumulateSingleSample(
    346        serviceFlags & nsIAuthModule::REQ_PROXY_AUTH
    347            ? NTLM_MODULE_KERBEROS_PROXY
    348            : NTLM_MODULE_KERBEROS_DIRECT);
    349    sTelemetrySent = true;
    350  }
    351 
    352  return NS_OK;
    353 }
    354 
    355 NS_IMETHODIMP
    356 nsAuthGSSAPI::GetNextToken(const void* inToken, uint32_t inTokenLen,
    357                           void** outToken, uint32_t* outTokenLen) {
    358  OM_uint32 major_status, minor_status;
    359  OM_uint32 req_flags = 0;
    360  OM_uint32 ret_flags = 0;
    361  gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
    362  gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
    363  gss_buffer_t in_token_ptr = GSS_C_NO_BUFFER;
    364  gss_name_t server;
    365  nsAutoCString userbuf;
    366  nsresult rv;
    367 
    368  LOG(("entering nsAuthGSSAPI::GetNextToken()\n"));
    369 
    370  if (!gssLibrary) return NS_ERROR_NOT_INITIALIZED;
    371 
    372  // If they've called us again after we're complete, reset to start afresh.
    373  if (mComplete) Reset();
    374 
    375  // Two-phase delegation logic
    376  // Phase 1: Try authentication without delegation first
    377  // Phase 2: Only retry with delegation if server supports it (ret_flags)
    378  bool delegationConfigured = (mServiceFlags & REQ_DELEGATE) != 0;
    379 
    380  if (delegationConfigured) {
    381    if (!mDelegationRequested) {
    382      // First attempt: don't request delegation yet
    383      LOG(("First auth attempt without delegation"));
    384      mDelegationRequested = true;
    385    } else if (mDelegationSupported) {
    386      // Second attempt: server supports delegation, now request it
    387      LOG(("Retrying auth with delegation - server supports it"));
    388      req_flags |= GSS_C_DELEG_FLAG;
    389    }
    390  }
    391 
    392  if (mServiceFlags & REQ_MUTUAL_AUTH) req_flags |= GSS_C_MUTUAL_FLAG;
    393 
    394  input_token.value = (void*)mServiceName.get();
    395  input_token.length = mServiceName.Length() + 1;
    396 
    397 #if defined(HAVE_RES_NINIT)
    398  res_ninit(&_res);
    399 #endif
    400  major_status = gss_import_name_ptr(&minor_status, &input_token,
    401                                     &gss_c_nt_hostbased_service, &server);
    402  input_token.value = nullptr;
    403  input_token.length = 0;
    404  if (GSS_ERROR(major_status)) {
    405    LogGssError(major_status, minor_status, "gss_import_name() failed");
    406    return NS_ERROR_FAILURE;
    407  }
    408 
    409  if (inToken) {
    410    input_token.length = inTokenLen;
    411    input_token.value = (void*)inToken;
    412    in_token_ptr = &input_token;
    413  } else if (mCtx != GSS_C_NO_CONTEXT) {
    414    // If there is no input token, then we are starting a new
    415    // authentication sequence.  If we have already initialized our
    416    // security context, then we're in trouble because it means that the
    417    // first sequence failed.  We need to bail or else we might end up in
    418    // an infinite loop.
    419    LOG(("Cannot restart authentication sequence!"));
    420    return NS_ERROR_UNEXPECTED;
    421  }
    422 
    423 #if defined(XP_MACOSX)
    424  // Suppress Kerberos prompts to get credentials.  See bug 240643.
    425  // We can only use Mac OS X specific kerb functions if we are using
    426  // the native lib
    427  KLBoolean found;
    428  bool doingMailTask = mServiceName.Find("imap@") ||
    429                       mServiceName.Find("pop@") ||
    430                       mServiceName.Find("smtp@") || mServiceName.Find("ldap@");
    431 
    432  if (!doingMailTask &&
    433      (gssNativeImp &&
    434       (KLCacheHasValidTickets_ptr(nullptr, kerberosVersion_V5, &found, nullptr,
    435                                   nullptr) != klNoErr ||
    436        !found))) {
    437    major_status = GSS_S_FAILURE;
    438    minor_status = 0;
    439  } else
    440 #endif /* XP_MACOSX */
    441    major_status = gss_init_sec_context_ptr(
    442        &minor_status, GSS_C_NO_CREDENTIAL, &mCtx, server, mMechOID, req_flags,
    443        GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, in_token_ptr, nullptr,
    444        &output_token, &ret_flags, nullptr);
    445 
    446  if (GSS_ERROR(major_status)) {
    447    LogGssError(major_status, minor_status, "gss_init_sec_context() failed");
    448    Reset();
    449    rv = NS_ERROR_FAILURE;
    450    goto end;
    451  }
    452  // Check if server supports delegation (OK-AS-DELEGATE equivalent)
    453  if (delegationConfigured && !mDelegationSupported &&
    454      (ret_flags & GSS_C_DELEG_FLAG)) {
    455    LOG(("Server supports delegation (GSS_C_DELEG_FLAG in ret_flags)"));
    456 
    457    // If we completed without requesting delegation, but server supports it,
    458    // we need to restart with delegation
    459    if (major_status == GSS_S_COMPLETE && !(req_flags & GSS_C_DELEG_FLAG)) {
    460      LOG(("Restarting authentication to request delegation"));
    461      Reset();
    462 
    463      // These flags get cleared by Reset().
    464      // Set them again to make sure the next call sets GSS_C_DELEG_FLAG
    465      mDelegationRequested = true;
    466      mDelegationSupported = true;
    467 
    468      gss_release_name_ptr(&minor_status, &server);
    469      return GetNextToken(inToken, inTokenLen, outToken, outTokenLen);
    470    }
    471  }
    472 
    473  if (major_status == GSS_S_COMPLETE) {
    474    // Mark ourselves as being complete, so that if we're called again
    475    // we know to start afresh.
    476    mComplete = true;
    477  } else if (major_status == GSS_S_CONTINUE_NEEDED) {
    478    //
    479    // The important thing is that we do NOT reset the
    480    // context here because it will be needed on the
    481    // next call.
    482    //
    483  }
    484 
    485  *outTokenLen = output_token.length;
    486  if (output_token.length != 0) {
    487    *outToken = moz_xmemdup(output_token.value, output_token.length);
    488  } else {
    489    *outToken = nullptr;
    490  }
    491 
    492  gss_release_buffer_ptr(&minor_status, &output_token);
    493 
    494  if (major_status == GSS_S_COMPLETE) {
    495    rv = NS_SUCCESS_AUTH_FINISHED;
    496  } else {
    497    rv = NS_OK;
    498  }
    499 
    500 end:
    501  gss_release_name_ptr(&minor_status, &server);
    502 
    503  LOG(("  leaving nsAuthGSSAPI::GetNextToken [rv=%" PRIx32 "]",
    504       static_cast<uint32_t>(rv)));
    505  return rv;
    506 }
    507 
    508 NS_IMETHODIMP
    509 nsAuthGSSAPI::Unwrap(const void* inToken, uint32_t inTokenLen, void** outToken,
    510                     uint32_t* outTokenLen) {
    511  OM_uint32 major_status, minor_status;
    512 
    513  gss_buffer_desc input_token;
    514  gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
    515 
    516  input_token.value = (void*)inToken;
    517  input_token.length = inTokenLen;
    518 
    519  major_status = gss_unwrap_ptr(&minor_status, mCtx, &input_token,
    520                                &output_token, nullptr, nullptr);
    521  if (GSS_ERROR(major_status)) {
    522    LogGssError(major_status, minor_status, "gss_unwrap() failed");
    523    Reset();
    524    gss_release_buffer_ptr(&minor_status, &output_token);
    525    return NS_ERROR_FAILURE;
    526  }
    527 
    528  *outTokenLen = output_token.length;
    529 
    530  if (output_token.length) {
    531    *outToken = moz_xmemdup(output_token.value, output_token.length);
    532  } else {
    533    *outToken = nullptr;
    534  }
    535 
    536  gss_release_buffer_ptr(&minor_status, &output_token);
    537 
    538  return NS_OK;
    539 }
    540 
    541 NS_IMETHODIMP
    542 nsAuthGSSAPI::Wrap(const void* inToken, uint32_t inTokenLen, bool confidential,
    543                   void** outToken, uint32_t* outTokenLen) {
    544  OM_uint32 major_status, minor_status;
    545 
    546  gss_buffer_desc input_token;
    547  gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
    548 
    549  input_token.value = (void*)inToken;
    550  input_token.length = inTokenLen;
    551 
    552  major_status =
    553      gss_wrap_ptr(&minor_status, mCtx, confidential, GSS_C_QOP_DEFAULT,
    554                   &input_token, nullptr, &output_token);
    555 
    556  if (GSS_ERROR(major_status)) {
    557    LogGssError(major_status, minor_status, "gss_wrap() failed");
    558    Reset();
    559    gss_release_buffer_ptr(&minor_status, &output_token);
    560    return NS_ERROR_FAILURE;
    561  }
    562 
    563  *outTokenLen = output_token.length;
    564 
    565  /* it is not possible for output_token.length to be zero */
    566  *outToken = moz_xmemdup(output_token.value, output_token.length);
    567  gss_release_buffer_ptr(&minor_status, &output_token);
    568 
    569  return NS_OK;
    570 }