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 // Negotiate 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 #include "nsAuthSSPI.h"
17 #include "nsComponentManagerUtils.h"
18 #include "nsDNSService2.h"
19 #include "nsIDNSService.h"
20 #include "nsIDNSRecord.h"
21 #include "nsMemory.h"
22 #include "nsNetCID.h"
23 #include "nsServiceManagerUtils.h"
24 #include "nsCOMPtr.h"
25 #include "nsICryptoHash.h"
26 #include "mozilla/Telemetry.h"
27
28 #include <windows.h>
29
30 #define SEC_SUCCESS(Status) ((Status) >= 0)
31
32 #ifndef KERB_WRAP_NO_ENCRYPT
33 # define KERB_WRAP_NO_ENCRYPT 0x80000001
34 #endif
35
36 #ifndef SECBUFFER_PADDING
37 # define SECBUFFER_PADDING 9
38 #endif
39
40 #ifndef SECBUFFER_STREAM
41 # define SECBUFFER_STREAM 10
42 #endif
43
44 //-----------------------------------------------------------------------------
45
46 static const wchar_t* const pTypeName[] = {L"Kerberos", L"Negotiate", L"NTLM"};
47
48 #ifdef DEBUG
49 # define CASE_(_x) \
50 case _x: \
51 return #_x;
MapErrorCode(int rc)52 static const char* MapErrorCode(int rc) {
53 switch (rc) {
54 CASE_(SEC_E_OK)
55 CASE_(SEC_I_CONTINUE_NEEDED)
56 CASE_(SEC_I_COMPLETE_NEEDED)
57 CASE_(SEC_I_COMPLETE_AND_CONTINUE)
58 CASE_(SEC_E_INCOMPLETE_MESSAGE)
59 CASE_(SEC_I_INCOMPLETE_CREDENTIALS)
60 CASE_(SEC_E_INVALID_HANDLE)
61 CASE_(SEC_E_TARGET_UNKNOWN)
62 CASE_(SEC_E_LOGON_DENIED)
63 CASE_(SEC_E_INTERNAL_ERROR)
64 CASE_(SEC_E_NO_CREDENTIALS)
65 CASE_(SEC_E_NO_AUTHENTICATING_AUTHORITY)
66 CASE_(SEC_E_INSUFFICIENT_MEMORY)
67 CASE_(SEC_E_INVALID_TOKEN)
68 }
69 return "<unknown>";
70 }
71 #else
72 # define MapErrorCode(_rc) ""
73 #endif
74
75 //-----------------------------------------------------------------------------
76
77 static PSecurityFunctionTableW sspi;
78
InitSSPI()79 static nsresult InitSSPI() {
80 LOG((" InitSSPI\n"));
81
82 sspi = InitSecurityInterfaceW();
83 if (!sspi) {
84 LOG(("InitSecurityInterfaceW failed"));
85 return NS_ERROR_UNEXPECTED;
86 }
87
88 return NS_OK;
89 }
90
91 //-----------------------------------------------------------------------------
92
MakeSN(const char * principal,nsCString & result)93 nsresult nsAuthSSPI::MakeSN(const char* principal, nsCString& result) {
94 nsresult rv;
95
96 nsAutoCString buf(principal);
97
98 // The service name looks like "protocol@hostname", we need to map
99 // this to a value that SSPI expects. To be consistent with IE, we
100 // need to map '@' to '/' and canonicalize the hostname.
101 int32_t index = buf.FindChar('@');
102 if (index == kNotFound) return NS_ERROR_UNEXPECTED;
103
104 nsCOMPtr<nsIDNSService> dnsService =
105 do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
106 if (NS_FAILED(rv)) return rv;
107
108 auto dns = static_cast<nsDNSService*>(dnsService.get());
109
110 // This could be expensive if our DNS cache cannot satisfy the request.
111 // However, we should have at least hit the OS resolver once prior to
112 // reaching this code, so provided the OS resolver has this information
113 // cached, we should not have to worry about blocking on this function call
114 // for very long. NOTE: because we ask for the canonical hostname, we
115 // might end up requiring extra network activity in cases where the OS
116 // resolver might not have enough information to satisfy the request from
117 // its cache. This is not an issue in versions of Windows up to WinXP.
118 nsCOMPtr<nsIDNSRecord> record;
119 mozilla::OriginAttributes attrs;
120 rv = dns->DeprecatedSyncResolve(Substring(buf, index + 1),
121 nsIDNSService::RESOLVE_CANONICAL_NAME, attrs,
122 getter_AddRefs(record));
123 if (NS_FAILED(rv)) return rv;
124 nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(record);
125 if (!rec) {
126 return NS_ERROR_UNEXPECTED;
127 }
128
129 nsAutoCString cname;
130 rv = rec->GetCanonicalName(cname);
131 if (NS_SUCCEEDED(rv)) {
132 result = StringHead(buf, index) + "/"_ns + cname;
133 LOG(("Using SPN of [%s]\n", result.get()));
134 }
135 return rv;
136 }
137
138 //-----------------------------------------------------------------------------
139
nsAuthSSPI(pType package)140 nsAuthSSPI::nsAuthSSPI(pType package)
141 : mServiceFlags(REQ_DEFAULT),
142 mMaxTokenLen(0),
143 mPackage(package),
144 mCertDERData(nullptr),
145 mCertDERLength(0) {
146 memset(&mCred, 0, sizeof(mCred));
147 memset(&mCtxt, 0, sizeof(mCtxt));
148 }
149
~nsAuthSSPI()150 nsAuthSSPI::~nsAuthSSPI() {
151 Reset();
152
153 if (mCred.dwLower || mCred.dwUpper) {
154 (sspi->FreeCredentialsHandle)(&mCred);
155 memset(&mCred, 0, sizeof(mCred));
156 }
157 }
158
Reset()159 void nsAuthSSPI::Reset() {
160 mIsFirst = true;
161
162 if (mCertDERData) {
163 free(mCertDERData);
164 mCertDERData = nullptr;
165 mCertDERLength = 0;
166 }
167
168 if (mCtxt.dwLower || mCtxt.dwUpper) {
169 (sspi->DeleteSecurityContext)(&mCtxt);
170 memset(&mCtxt, 0, sizeof(mCtxt));
171 }
172 }
173
NS_IMPL_ISUPPORTS(nsAuthSSPI,nsIAuthModule)174 NS_IMPL_ISUPPORTS(nsAuthSSPI, nsIAuthModule)
175
176 NS_IMETHODIMP
177 nsAuthSSPI::Init(const char* serviceName, uint32_t serviceFlags,
178 const char16_t* domain, const char16_t* username,
179 const char16_t* password) {
180 LOG((" nsAuthSSPI::Init\n"));
181
182 mIsFirst = true;
183 mCertDERLength = 0;
184 mCertDERData = nullptr;
185
186 // The caller must supply a service name to be used. (For why we now require
187 // a service name for NTLM, see bug 487872.)
188 NS_ENSURE_TRUE(serviceName && *serviceName, NS_ERROR_INVALID_ARG);
189
190 nsresult rv;
191
192 // XXX lazy initialization like this assumes that we are single threaded
193 if (!sspi) {
194 rv = InitSSPI();
195 if (NS_FAILED(rv)) return rv;
196 }
197 SEC_WCHAR* package;
198
199 package = (SEC_WCHAR*)pTypeName[(int)mPackage];
200
201 if (mPackage == PACKAGE_TYPE_NTLM) {
202 // (bug 535193) For NTLM, just use the uri host, do not do canonical host
203 // lookups. The incoming serviceName is in the format: "protocol@hostname",
204 // SSPI expects
205 // "<service class>/<hostname>", so swap the '@' for a '/'.
206 mServiceName.Assign(serviceName);
207 int32_t index = mServiceName.FindChar('@');
208 if (index == kNotFound) return NS_ERROR_UNEXPECTED;
209 mServiceName.Replace(index, 1, '/');
210 } else {
211 // Kerberos requires the canonical host, MakeSN takes care of this through a
212 // DNS lookup.
213 rv = MakeSN(serviceName, mServiceName);
214 if (NS_FAILED(rv)) return rv;
215 }
216
217 mServiceFlags = serviceFlags;
218
219 SECURITY_STATUS rc;
220
221 PSecPkgInfoW pinfo;
222 rc = (sspi->QuerySecurityPackageInfoW)(package, &pinfo);
223 if (rc != SEC_E_OK) {
224 LOG(("%s package not found\n", package));
225 return NS_ERROR_UNEXPECTED;
226 }
227 mMaxTokenLen = pinfo->cbMaxToken;
228 (sspi->FreeContextBuffer)(pinfo);
229
230 MS_TimeStamp useBefore;
231
232 SEC_WINNT_AUTH_IDENTITY_W ai;
233 SEC_WINNT_AUTH_IDENTITY_W* pai = nullptr;
234
235 // domain, username, and password will be null if nsHttpNTLMAuth's
236 // ChallengeReceived returns false for identityInvalid. Use default
237 // credentials in this case by passing null for pai.
238 if (username && password) {
239 // Keep a copy of these strings for the duration
240 mUsername.Assign(username);
241 mPassword.Assign(password);
242 mDomain.Assign(domain);
243 ai.Domain = reinterpret_cast<unsigned short*>(mDomain.BeginWriting());
244 ai.DomainLength = mDomain.Length();
245 ai.User = reinterpret_cast<unsigned short*>(mUsername.BeginWriting());
246 ai.UserLength = mUsername.Length();
247 ai.Password = reinterpret_cast<unsigned short*>(mPassword.BeginWriting());
248 ai.PasswordLength = mPassword.Length();
249 ai.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
250 pai = &ai;
251 }
252
253 rc = (sspi->AcquireCredentialsHandleW)(nullptr, package, SECPKG_CRED_OUTBOUND,
254 nullptr, pai, nullptr, nullptr, &mCred,
255 &useBefore);
256 if (rc != SEC_E_OK) return NS_ERROR_UNEXPECTED;
257
258 static bool sTelemetrySent = false;
259 if (!sTelemetrySent) {
260 mozilla::Telemetry::Accumulate(mozilla::Telemetry::NTLM_MODULE_USED_2,
261 serviceFlags & nsIAuthModule::REQ_PROXY_AUTH
262 ? NTLM_MODULE_WIN_API_PROXY
263 : NTLM_MODULE_WIN_API_DIRECT);
264 sTelemetrySent = true;
265 }
266
267 LOG(("AcquireCredentialsHandle() succeeded.\n"));
268 return NS_OK;
269 }
270
271 // The arguments inToken and inTokenLen are used to pass in the server
272 // certificate (when available) in the first call of the function. The
273 // second time these arguments hold an input token.
274 NS_IMETHODIMP
GetNextToken(const void * inToken,uint32_t inTokenLen,void ** outToken,uint32_t * outTokenLen)275 nsAuthSSPI::GetNextToken(const void* inToken, uint32_t inTokenLen,
276 void** outToken, uint32_t* outTokenLen) {
277 // String for end-point bindings.
278 const char end_point[] = "tls-server-end-point:";
279 const int end_point_length = sizeof(end_point) - 1;
280 const int hash_size = 32; // Size of a SHA256 hash.
281 const int cbt_size = hash_size + end_point_length;
282
283 SECURITY_STATUS rc;
284 MS_TimeStamp ignored;
285
286 DWORD ctxAttr, ctxReq = 0;
287 CtxtHandle* ctxIn;
288 SecBufferDesc ibd, obd;
289 // Optional second input buffer for the CBT (Channel Binding Token)
290 SecBuffer ib[2], ob;
291 // Pointer to the block of memory that stores the CBT
292 char* sspi_cbt = nullptr;
293 SEC_CHANNEL_BINDINGS pendpoint_binding;
294
295 LOG(("entering nsAuthSSPI::GetNextToken()\n"));
296
297 if (!mCred.dwLower && !mCred.dwUpper) {
298 LOG(("nsAuthSSPI::GetNextToken(), not initialized. exiting."));
299 return NS_ERROR_NOT_INITIALIZED;
300 }
301
302 if (mServiceFlags & REQ_DELEGATE) ctxReq |= ISC_REQ_DELEGATE;
303 if (mServiceFlags & REQ_MUTUAL_AUTH) ctxReq |= ISC_REQ_MUTUAL_AUTH;
304
305 if (inToken) {
306 if (mIsFirst) {
307 // First time if it comes with a token,
308 // the token represents the server certificate.
309 mIsFirst = false;
310 mCertDERLength = inTokenLen;
311 mCertDERData = moz_xmalloc(inTokenLen);
312 memcpy(mCertDERData, inToken, inTokenLen);
313
314 // We are starting a new authentication sequence.
315 // If we have already initialized our
316 // security context, then we're in trouble because it means that the
317 // first sequence failed. We need to bail or else we might end up in
318 // an infinite loop.
319 if (mCtxt.dwLower || mCtxt.dwUpper) {
320 LOG(("Cannot restart authentication sequence!"));
321 return NS_ERROR_UNEXPECTED;
322 }
323 ctxIn = nullptr;
324 // The certificate needs to be erased before being passed
325 // to InitializeSecurityContextW().
326 inToken = nullptr;
327 inTokenLen = 0;
328 } else {
329 ibd.ulVersion = SECBUFFER_VERSION;
330 ibd.cBuffers = 0;
331 ibd.pBuffers = ib;
332
333 // If we have stored a certificate, the Channel Binding Token
334 // needs to be generated and sent in the first input buffer.
335 if (mCertDERLength > 0) {
336 // First we create a proper Endpoint Binding structure.
337 pendpoint_binding.dwInitiatorAddrType = 0;
338 pendpoint_binding.cbInitiatorLength = 0;
339 pendpoint_binding.dwInitiatorOffset = 0;
340 pendpoint_binding.dwAcceptorAddrType = 0;
341 pendpoint_binding.cbAcceptorLength = 0;
342 pendpoint_binding.dwAcceptorOffset = 0;
343 pendpoint_binding.cbApplicationDataLength = cbt_size;
344 pendpoint_binding.dwApplicationDataOffset =
345 sizeof(SEC_CHANNEL_BINDINGS);
346
347 // Then add it to the array of sec buffers accordingly.
348 ib[ibd.cBuffers].BufferType = SECBUFFER_CHANNEL_BINDINGS;
349 ib[ibd.cBuffers].cbBuffer = pendpoint_binding.cbApplicationDataLength +
350 pendpoint_binding.dwApplicationDataOffset;
351
352 sspi_cbt = (char*)moz_xmalloc(ib[ibd.cBuffers].cbBuffer);
353
354 // Helper to write in the memory block that stores the CBT
355 char* sspi_cbt_ptr = sspi_cbt;
356
357 ib[ibd.cBuffers].pvBuffer = sspi_cbt;
358 ibd.cBuffers++;
359
360 memcpy(sspi_cbt_ptr, &pendpoint_binding,
361 pendpoint_binding.dwApplicationDataOffset);
362 sspi_cbt_ptr += pendpoint_binding.dwApplicationDataOffset;
363
364 memcpy(sspi_cbt_ptr, end_point, end_point_length);
365 sspi_cbt_ptr += end_point_length;
366
367 // Start hashing. We are always doing SHA256, but depending
368 // on the certificate, a different alogirthm might be needed.
369 nsAutoCString hashString;
370
371 nsresult rv;
372 nsCOMPtr<nsICryptoHash> crypto;
373 crypto = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
374 if (NS_SUCCEEDED(rv)) rv = crypto->Init(nsICryptoHash::SHA256);
375 if (NS_SUCCEEDED(rv))
376 rv = crypto->Update((unsigned char*)mCertDERData, mCertDERLength);
377 if (NS_SUCCEEDED(rv)) rv = crypto->Finish(false, hashString);
378 if (NS_FAILED(rv)) {
379 free(mCertDERData);
380 mCertDERData = nullptr;
381 mCertDERLength = 0;
382 free(sspi_cbt);
383 return rv;
384 }
385
386 // Once the hash has been computed, we store it in memory right
387 // after the Endpoint structure and the "tls-server-end-point:"
388 // char array.
389 memcpy(sspi_cbt_ptr, hashString.get(), hash_size);
390
391 // Free memory used to store the server certificate
392 free(mCertDERData);
393 mCertDERData = nullptr;
394 mCertDERLength = 0;
395 } // End of CBT computation.
396
397 // We always need this SECBUFFER.
398 ib[ibd.cBuffers].BufferType = SECBUFFER_TOKEN;
399 ib[ibd.cBuffers].cbBuffer = inTokenLen;
400 ib[ibd.cBuffers].pvBuffer = (void*)inToken;
401 ibd.cBuffers++;
402 ctxIn = &mCtxt;
403 }
404 } else { // First time and without a token (no server certificate)
405 // We are starting a new authentication sequence. If we have already
406 // initialized our security context, then we're in trouble because it
407 // means that the first sequence failed. We need to bail or else we
408 // might end up in an infinite loop.
409 if (mCtxt.dwLower || mCtxt.dwUpper || mCertDERData || mCertDERLength) {
410 LOG(("Cannot restart authentication sequence!"));
411 return NS_ERROR_UNEXPECTED;
412 }
413 ctxIn = nullptr;
414 mIsFirst = false;
415 }
416
417 obd.ulVersion = SECBUFFER_VERSION;
418 obd.cBuffers = 1;
419 obd.pBuffers = &ob;
420 ob.BufferType = SECBUFFER_TOKEN;
421 ob.cbBuffer = mMaxTokenLen;
422 ob.pvBuffer = moz_xmalloc(ob.cbBuffer);
423 memset(ob.pvBuffer, 0, ob.cbBuffer);
424
425 NS_ConvertUTF8toUTF16 wSN(mServiceName);
426 SEC_WCHAR* sn = (SEC_WCHAR*)wSN.get();
427
428 rc = (sspi->InitializeSecurityContextW)(
429 &mCred, ctxIn, sn, ctxReq, 0, SECURITY_NATIVE_DREP,
430 inToken ? &ibd : nullptr, 0, &mCtxt, &obd, &ctxAttr, &ignored);
431 if (rc == SEC_I_CONTINUE_NEEDED || rc == SEC_E_OK) {
432 if (rc == SEC_E_OK)
433 LOG(("InitializeSecurityContext: succeeded.\n"));
434 else
435 LOG(("InitializeSecurityContext: continue.\n"));
436
437 if (sspi_cbt) free(sspi_cbt);
438
439 if (!ob.cbBuffer) {
440 free(ob.pvBuffer);
441 ob.pvBuffer = nullptr;
442 }
443 *outToken = ob.pvBuffer;
444 *outTokenLen = ob.cbBuffer;
445
446 if (rc == SEC_E_OK) return NS_SUCCESS_AUTH_FINISHED;
447
448 return NS_OK;
449 }
450
451 LOG(("InitializeSecurityContext failed [rc=%d:%s]\n", rc, MapErrorCode(rc)));
452 Reset();
453 free(ob.pvBuffer);
454 return NS_ERROR_FAILURE;
455 }
456
457 NS_IMETHODIMP
Unwrap(const void * inToken,uint32_t inTokenLen,void ** outToken,uint32_t * outTokenLen)458 nsAuthSSPI::Unwrap(const void* inToken, uint32_t inTokenLen, void** outToken,
459 uint32_t* outTokenLen) {
460 SECURITY_STATUS rc;
461 SecBufferDesc ibd;
462 SecBuffer ib[2];
463
464 ibd.cBuffers = 2;
465 ibd.pBuffers = ib;
466 ibd.ulVersion = SECBUFFER_VERSION;
467
468 // SSPI Buf
469 ib[0].BufferType = SECBUFFER_STREAM;
470 ib[0].cbBuffer = inTokenLen;
471 ib[0].pvBuffer = moz_xmalloc(ib[0].cbBuffer);
472
473 memcpy(ib[0].pvBuffer, inToken, inTokenLen);
474
475 // app data
476 ib[1].BufferType = SECBUFFER_DATA;
477 ib[1].cbBuffer = 0;
478 ib[1].pvBuffer = nullptr;
479
480 rc = (sspi->DecryptMessage)(&mCtxt, &ibd,
481 0, // no sequence numbers
482 nullptr);
483
484 if (SEC_SUCCESS(rc)) {
485 // check if ib[1].pvBuffer is really just ib[0].pvBuffer, in which
486 // case we can let the caller free it. Otherwise, we need to
487 // clone it, and free the original
488 if (ib[0].pvBuffer == ib[1].pvBuffer) {
489 *outToken = ib[1].pvBuffer;
490 } else {
491 *outToken = moz_xmemdup(ib[1].pvBuffer, ib[1].cbBuffer);
492 free(ib[0].pvBuffer);
493 }
494 *outTokenLen = ib[1].cbBuffer;
495 } else
496 free(ib[0].pvBuffer);
497
498 if (!SEC_SUCCESS(rc)) return NS_ERROR_FAILURE;
499
500 return NS_OK;
501 }
502
503 // utility class used to free memory on exit
504 class secBuffers {
505 public:
506 SecBuffer ib[3];
507
secBuffers()508 secBuffers() { memset(&ib, 0, sizeof(ib)); }
509
~secBuffers()510 ~secBuffers() {
511 if (ib[0].pvBuffer) free(ib[0].pvBuffer);
512
513 if (ib[1].pvBuffer) free(ib[1].pvBuffer);
514
515 if (ib[2].pvBuffer) free(ib[2].pvBuffer);
516 }
517 };
518
519 NS_IMETHODIMP
Wrap(const void * inToken,uint32_t inTokenLen,bool confidential,void ** outToken,uint32_t * outTokenLen)520 nsAuthSSPI::Wrap(const void* inToken, uint32_t inTokenLen, bool confidential,
521 void** outToken, uint32_t* outTokenLen) {
522 SECURITY_STATUS rc;
523
524 SecBufferDesc ibd;
525 secBuffers bufs;
526 SecPkgContext_Sizes sizes;
527
528 rc = (sspi->QueryContextAttributesW)(&mCtxt, SECPKG_ATTR_SIZES, &sizes);
529
530 if (!SEC_SUCCESS(rc)) return NS_ERROR_FAILURE;
531
532 ibd.cBuffers = 3;
533 ibd.pBuffers = bufs.ib;
534 ibd.ulVersion = SECBUFFER_VERSION;
535
536 // SSPI
537 bufs.ib[0].cbBuffer = sizes.cbSecurityTrailer;
538 bufs.ib[0].BufferType = SECBUFFER_TOKEN;
539 bufs.ib[0].pvBuffer = moz_xmalloc(sizes.cbSecurityTrailer);
540
541 // APP Data
542 bufs.ib[1].BufferType = SECBUFFER_DATA;
543 bufs.ib[1].pvBuffer = moz_xmalloc(inTokenLen);
544 bufs.ib[1].cbBuffer = inTokenLen;
545
546 memcpy(bufs.ib[1].pvBuffer, inToken, inTokenLen);
547
548 // SSPI
549 bufs.ib[2].BufferType = SECBUFFER_PADDING;
550 bufs.ib[2].cbBuffer = sizes.cbBlockSize;
551 bufs.ib[2].pvBuffer = moz_xmalloc(bufs.ib[2].cbBuffer);
552
553 rc = (sspi->EncryptMessage)(&mCtxt, confidential ? 0 : KERB_WRAP_NO_ENCRYPT,
554 &ibd, 0);
555
556 if (SEC_SUCCESS(rc)) {
557 int len = bufs.ib[0].cbBuffer + bufs.ib[1].cbBuffer + bufs.ib[2].cbBuffer;
558 char* p = (char*)moz_xmalloc(len);
559
560 *outToken = (void*)p;
561 *outTokenLen = len;
562
563 memcpy(p, bufs.ib[0].pvBuffer, bufs.ib[0].cbBuffer);
564 p += bufs.ib[0].cbBuffer;
565
566 memcpy(p, bufs.ib[1].pvBuffer, bufs.ib[1].cbBuffer);
567 p += bufs.ib[1].cbBuffer;
568
569 memcpy(p, bufs.ib[2].pvBuffer, bufs.ib[2].cbBuffer);
570
571 return NS_OK;
572 }
573
574 return NS_ERROR_FAILURE;
575 }
576