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