ses: SUN plugin doesn't actually need libnvfru
[unleashed.git] / usr / src / lib / libsmbfs / smb / ntlmssp.c
blob8e8dcb8454debf53c63b396087c214e07ffd084b
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
19 * CDDL HEADER END
23 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
28 * NT Lan Manager Security Support Provider (NTLMSSP)
30 * Based on information from the "Davenport NTLM" page:
31 * http://davenport.sourceforge.net/ntlm.html
35 #include <errno.h>
36 #include <stdio.h>
37 #include <stddef.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 #include <strings.h>
41 #include <netdb.h>
42 #include <libintl.h>
43 #include <xti.h>
44 #include <assert.h>
46 #include <sys/types.h>
47 #include <sys/time.h>
48 #include <sys/byteorder.h>
49 #include <sys/socket.h>
50 #include <sys/fcntl.h>
52 #include <netinet/in.h>
53 #include <netinet/tcp.h>
54 #include <arpa/inet.h>
56 #include <netsmb/smb.h>
57 #include <netsmb/smb_lib.h>
58 #include <netsmb/mchain.h>
60 #include "private.h"
61 #include "charsets.h"
62 #include "smb_crypt.h"
63 #include "spnego.h"
64 #include "derparse.h"
65 #include "ssp.h"
66 #include "ntlm.h"
67 #include "ntlmssp.h"
69 /* A shorter alias for a crazy long name from [MS-NLMP] */
70 #define NTLMSSP_NEGOTIATE_NTLM2 \
71 NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
73 typedef struct ntlmssp_state {
74 uint32_t ss_flags;
75 char *ss_target_name; /* Primary domain or server name */
76 struct mbuf *ss_target_info;
77 uchar_t ss_kxkey[NTLM_HASH_SZ];
78 } ntlmssp_state_t;
81 * So called "security buffer".
82 * A lot like an RPC string.
84 struct sec_buf {
85 uint16_t sb_length;
86 uint16_t sb_maxlen;
87 uint32_t sb_offset;
89 #define ID_SZ 8
90 static const char ntlmssp_id[ID_SZ] = "NTLMSSP";
92 static int
93 ntlm_rand_ssn_key(struct smb_ctx *ctx,
94 ntlmssp_state_t *ssp_st, struct mbdata *ek_mbp);
97 * Get a "security buffer" (header part)
99 static int
100 md_get_sb_hdr(struct mbdata *mbp, struct sec_buf *sb)
102 int err;
104 (void) md_get_uint16le(mbp, &sb->sb_length);
105 (void) md_get_uint16le(mbp, &sb->sb_maxlen);
106 err = md_get_uint32le(mbp, &sb->sb_offset);
108 return (err);
112 * Get a "security buffer" (data part), where
113 * the data is delivered as an mbuf.
115 static int
116 md_get_sb_data(struct mbdata *mbp, struct sec_buf *sb, struct mbuf **mp)
118 struct mbdata tmp_mb;
119 int err;
122 * Setup tmp_mb to point to the start of the header.
123 * This is a dup ref - do NOT free it.
125 mb_initm(&tmp_mb, mbp->mb_top);
127 /* Skip data up to the offset. */
128 err = md_get_mem(&tmp_mb, NULL, sb->sb_offset, MB_MSYSTEM);
129 if (err)
130 return (err);
132 /* Get the data (as an mbuf). */
133 err = md_get_mbuf(&tmp_mb, sb->sb_maxlen, mp);
135 return (err);
139 * Put a "security buffer" (header part)
141 static int
142 mb_put_sb_hdr(struct mbdata *mbp, struct sec_buf *sb)
144 int err;
146 (void) mb_put_uint16le(mbp, sb->sb_length);
147 (void) mb_put_uint16le(mbp, sb->sb_maxlen);
148 err = mb_put_uint32le(mbp, sb->sb_offset);
150 return (err);
154 * Put a "security buffer" (data part), where
155 * the data is an mbuf. Note: consumes m.
157 static int
158 mb_put_sb_data(struct mbdata *mbp, struct sec_buf *sb, struct mbuf *m)
160 int cnt0;
161 int err = 0;
163 sb->sb_offset = cnt0 = mbp->mb_count;
164 if (m != NULL)
165 err = mb_put_mbuf(mbp, m);
166 sb->sb_maxlen = sb->sb_length = mbp->mb_count - cnt0;
168 return (err);
172 * Put a "security buffer" (data part), where
173 * the data is a string (OEM or unicode).
175 * The string is NOT null terminated.
177 static int
178 mb_put_sb_string(struct mbdata *mbp, struct sec_buf *sb,
179 const char *str, int unicode)
181 int err, trim;
182 struct mbdata tmp_mb;
184 bzero(&tmp_mb, sizeof (tmp_mb));
186 if (str != NULL && *str != '\0') {
188 * Put the string into a temp. mbuf,
189 * then chop off the null terminator
190 * before appending to caller's mbp.
192 err = mb_init(&tmp_mb);
193 if (err)
194 return (err);
195 err = mb_put_string(&tmp_mb, str, unicode);
196 if (err)
197 return (err);
199 trim = (unicode) ? 2 : 1;
200 if (tmp_mb.mb_cur->m_len < trim)
201 trim = 0;
202 tmp_mb.mb_cur->m_len -= trim;
205 err = mb_put_sb_data(mbp, sb, tmp_mb.mb_top);
207 * Note: tmp_mb.mb_top (if any) is consumed,
208 * so do NOT free it (no mb_done)
210 return (err);
214 * Build a Type 1 message
216 * This message has a header section containing offsets to
217 * data later in the message. We use the common trick of
218 * building it in two parts and then concatenatening.
221 ntlmssp_put_type1(struct ssp_ctx *sp, struct mbdata *out_mb)
223 struct type1hdr {
224 char h_id[ID_SZ];
225 uint32_t h_type;
226 uint32_t h_flags;
227 struct sec_buf h_cldom;
228 struct sec_buf h_wksta;
229 } hdr;
230 struct mbdata mb2; /* 2nd part */
231 int err;
232 struct smb_ctx *ctx = sp->smb_ctx;
233 ntlmssp_state_t *ssp_st = sp->sp_private;
235 if ((err = mb_init(&mb2)) != 0)
236 return (err);
237 mb2.mb_count = sizeof (hdr);
240 * The initial negotiation flags represent the union of all
241 * options we support. The server selects from these.
242 * See: [MS-NLMP 2.2.2.5 NEGOTIATE]
244 ssp_st->ss_flags =
245 NTLMSSP_NEGOTIATE_UNICODE |
246 NTLMSSP_NEGOTIATE_OEM |
247 NTLMSSP_REQUEST_TARGET |
248 NTLMSSP_NEGOTIATE_SIGN |
249 NTLMSSP_NEGOTIATE_SEAL |
250 /* NTLMSSP_NEGOTIATE_LM_KEY (never) */
251 NTLMSSP_NEGOTIATE_NTLM |
252 /* NTLMSSP_NEGOTIATE_ALWAYS_SIGN (set below) */
253 NTLMSSP_NEGOTIATE_NTLM2 |
254 NTLMSSP_NEGOTIATE_128 |
255 NTLMSSP_NEGOTIATE_KEY_EXCH |
256 NTLMSSP_NEGOTIATE_56;
258 if (ctx->ct_vcflags & SMBV_WILL_SIGN) {
259 ssp_st->ss_flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
260 ctx->ct_hflags2 |= SMB_FLAGS2_SECURITY_SIGNATURE;
263 bcopy(ntlmssp_id, &hdr.h_id, ID_SZ);
264 hdr.h_type = NTLMSSP_MSGTYPE_NEGOTIATE;
265 hdr.h_flags = ssp_st->ss_flags;
268 * We could put the client domain, client name strings
269 * here, (always in OEM format, upper-case), and set
270 * NTLMSSP_NEGOTIATE_OEM_..._SUPPLIED, but Windows
271 * leaves these NULL so let's do the same.
273 (void) mb_put_sb_string(&mb2, &hdr.h_cldom, NULL, 0);
274 (void) mb_put_sb_string(&mb2, &hdr.h_wksta, NULL, 0);
277 * Marshal the header (in LE order)
278 * then concatenate the 2nd part.
280 (void) mb_put_mem(out_mb, &hdr.h_id, ID_SZ, MB_MSYSTEM);
281 (void) mb_put_uint32le(out_mb, hdr.h_type);
282 (void) mb_put_uint32le(out_mb, hdr.h_flags);
283 (void) mb_put_sb_hdr(out_mb, &hdr.h_cldom);
284 (void) mb_put_sb_hdr(out_mb, &hdr.h_wksta);
286 err = mb_put_mbuf(out_mb, mb2.mb_top);
288 return (err);
292 * Parse a Type 2 message
295 ntlmssp_get_type2(struct ssp_ctx *sp, struct mbdata *in_mb)
297 struct type2hdr {
298 char h_id[ID_SZ];
299 uint32_t h_type;
300 struct sec_buf h_target_name;
301 uint32_t h_flags;
302 uint8_t h_challenge[8];
303 uint32_t h_context[2]; /* optional */
304 struct sec_buf h_target_info; /* optional */
305 } hdr;
306 struct mbdata top_mb, tmp_mb;
307 struct mbuf *m;
308 int err, uc;
309 int min_hdr_sz = offsetof(struct type2hdr, h_context);
310 struct smb_ctx *ctx = sp->smb_ctx;
311 ntlmssp_state_t *ssp_st = sp->sp_private;
312 char *buf = NULL;
314 if (m_totlen(in_mb->mb_top) < min_hdr_sz) {
315 err = EBADRPC;
316 goto out;
320 * Save the mbdata pointers before we consume anything.
321 * Careful to NOT free this (would be dup. free)
322 * We use this below to find data based on offsets
323 * from the start of the header.
325 top_mb = *in_mb;
327 /* Parse the fixed size header stuff. */
328 bzero(&hdr, sizeof (hdr));
329 (void) md_get_mem(in_mb, &hdr.h_id, ID_SZ, MB_MSYSTEM);
330 (void) md_get_uint32le(in_mb, &hdr.h_type);
331 if (hdr.h_type != NTLMSSP_MSGTYPE_CHALLENGE) {
332 err = EPROTO;
333 goto out;
335 (void) md_get_sb_hdr(in_mb, &hdr.h_target_name);
336 (void) md_get_uint32le(in_mb, &hdr.h_flags);
337 (void) md_get_mem(in_mb, &hdr.h_challenge, NTLM_CHAL_SZ, MB_MSYSTEM);
340 * Save flags, server challenge for later.
342 ssp_st->ss_flags = hdr.h_flags;
343 bcopy(&hdr.h_challenge, ctx->ct_srv_chal, NTLM_CHAL_SZ);
346 * Turn off flags that might have been given but
347 * that we don't want to send with authenticate.
349 uc = hdr.h_flags & NTLMSSP_NEGOTIATE_UNICODE;
350 ssp_st->ss_flags &= ~NTLMSSP_NEGOTIATE_VERSION;
353 * Now find out if the optional parts are there.
355 if ((m_totlen(top_mb.mb_top) > sizeof (hdr)) &&
356 (hdr.h_target_name.sb_offset >= sizeof (hdr))) {
357 (void) md_get_uint32le(in_mb, &hdr.h_context[0]);
358 (void) md_get_uint32le(in_mb, &hdr.h_context[1]);
359 (void) md_get_sb_hdr(in_mb, &hdr.h_target_info);
363 * Get the target name string. (Server name or
364 * Primary domain name.) First get a copy of the
365 * data from the offset/length indicated in the
366 * security buffer header; then parse the string.
368 err = md_get_sb_data(&top_mb, &hdr.h_target_name, &m);
369 if (err)
370 goto out;
371 mb_initm(&tmp_mb, m);
372 err = md_get_string(&tmp_mb, &ssp_st->ss_target_name, uc);
373 mb_done(&tmp_mb);
376 * Get the target info blob, if present.
378 if (hdr.h_target_info.sb_offset >= sizeof (hdr)) {
379 err = md_get_sb_data(&top_mb, &hdr.h_target_info,
380 &ssp_st->ss_target_info);
383 out:
384 free(buf);
386 return (err);
390 * Build a Type 3 message
392 * This message has a header section containing offsets to
393 * data later in the message. We use the common trick of
394 * building it in two parts and then concatenatening.
397 ntlmssp_put_type3(struct ssp_ctx *sp, struct mbdata *out_mb)
399 struct type3hdr {
400 char h_id[ID_SZ];
401 uint32_t h_type;
402 struct sec_buf h_lm_resp;
403 struct sec_buf h_nt_resp;
404 struct sec_buf h_domain;
405 struct sec_buf h_user;
406 struct sec_buf h_wksta;
407 struct sec_buf h_ssn_key;
408 uint32_t h_flags;
409 /* Version struct (ommitted) */
410 uchar_t h_mic[NTLM_HASH_SZ];
411 } hdr;
412 struct mbdata lm_mbc; /* LM response */
413 struct mbdata nt_mbc; /* NT response */
414 struct mbdata ti_mbc; /* target info */
415 struct mbdata ek_mbc; /* encrypted session key */
416 struct mbdata mb2; /* payload */
417 int err, uc;
418 struct smb_ctx *ctx = sp->smb_ctx;
419 ntlmssp_state_t *ssp_st = sp->sp_private;
420 uchar_t *pmic;
422 bzero(&hdr, sizeof (hdr));
423 bzero(&lm_mbc, sizeof (lm_mbc));
424 bzero(&nt_mbc, sizeof (nt_mbc));
425 bzero(&ti_mbc, sizeof (ti_mbc));
426 bzero(&ek_mbc, sizeof (ek_mbc));
427 bzero(&mb2, sizeof (mb2));
430 * Fill in the NTLMSSP header, etc.
432 if ((err = mb_init(&mb2)) != 0)
433 goto out;
434 mb2.mb_count = sizeof (hdr);
435 uc = ssp_st->ss_flags & NTLMSSP_NEGOTIATE_UNICODE;
437 bcopy(ntlmssp_id, &hdr.h_id, ID_SZ);
438 hdr.h_type = NTLMSSP_MSGTYPE_AUTHENTICATE;
439 hdr.h_flags = ssp_st->ss_flags;
442 * Put the NTLMv2/LMv2 or NTLM/LM (v1) responses,
443 * and compute the session key, etc.
445 if (ctx->ct_authflags & SMB_AT_ANON) {
447 * We're setting up a NULL session, meaning
448 * the lm_mbc, nt_mbc parts remain empty.
449 * Let's add the "anon" flag (hint).
450 * As there is no session key, disable the
451 * fancy session key stuff.
453 hdr.h_flags |= NTLMSSP_NEGOTIATE_NULL_SESSION;
454 ssp_st->ss_flags &= ~(
455 NTLMSSP_NEGOTIATE_NTLM2 |
456 NTLMSSP_NEGOTIATE_KEY_EXCH);
457 err = 0;
458 } else if (ctx->ct_authflags & SMB_AT_NTLM2) {
460 * Doing NTLMv2/LMv2
462 err = ntlm_build_target_info(ctx,
463 ssp_st->ss_target_info, &ti_mbc);
464 if (err)
465 goto out;
466 err = ntlm_put_v2_responses(ctx, &ti_mbc,
467 &lm_mbc, &nt_mbc);
468 if (err)
469 goto out;
470 /* The "key exg. key" is the session base key */
471 memcpy(ssp_st->ss_kxkey, ctx->ct_ssn_key, NTLM_HASH_SZ);
473 } else if (ssp_st->ss_flags & NTLMSSP_NEGOTIATE_NTLM2) {
475 * Doing NTLM ("v1x") which is NTLM with
476 * "Extended Session Security"
478 err = ntlm_put_v1x_responses(ctx,
479 &lm_mbc, &nt_mbc);
480 if (err)
481 goto out;
482 /* Compute the "Key exchange key". */
483 ntlm2_kxkey(ctx, &lm_mbc, ssp_st->ss_kxkey);
484 } else {
486 * Doing plain old NTLM (and LM if enabled)
488 err = ntlm_put_v1_responses(ctx,
489 &lm_mbc, &nt_mbc);
490 if (err)
491 goto out;
492 /* The "key exg. key" is the session base key */
493 memcpy(ssp_st->ss_kxkey, ctx->ct_ssn_key, NTLM_HASH_SZ);
497 * Compute the "Exported Session Key" and (possibly)
498 * the "Encrypted Random Sesion Key".
499 * [MS-NLMP 3.1.5.1.2]
501 if (ssp_st->ss_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
502 err = ntlm_rand_ssn_key(ctx, ssp_st, &ek_mbc);
503 if (err)
504 goto out;
505 } else {
506 /* ExportedSessionKey is the KeyExchangeKey */
507 memcpy(ctx->ct_ssn_key, ssp_st->ss_kxkey, NTLM_HASH_SZ);
508 /* EncryptedRandomSessionKey remains NULL */
511 err = mb_put_sb_data(&mb2, &hdr.h_lm_resp, lm_mbc.mb_top);
512 lm_mbc.mb_top = NULL; /* consumed */
513 if (err)
514 goto out;
515 err = mb_put_sb_data(&mb2, &hdr.h_nt_resp, nt_mbc.mb_top);
516 nt_mbc.mb_top = NULL; /* consumed */
517 if (err)
518 goto out;
521 * Put the "target" (domain), user, workstation
523 err = mb_put_sb_string(&mb2, &hdr.h_domain, ctx->ct_domain, uc);
524 if (err)
525 goto out;
526 err = mb_put_sb_string(&mb2, &hdr.h_user, ctx->ct_user, uc);
527 if (err)
528 goto out;
529 err = mb_put_sb_string(&mb2, &hdr.h_wksta, ctx->ct_locname, uc);
530 if (err)
531 goto out;
534 * Put the "Encrypted Random Session Key", if any.
535 * (ek_mbc.mb_top may be NULL)
537 err = mb_put_sb_data(&mb2, &hdr.h_ssn_key, ek_mbc.mb_top);
538 ek_mbc.mb_top = NULL; /* consumed (if any) */
539 if (err)
540 goto out;
543 * Marshal the header (in LE order)
544 * then concatenate the 2nd part.
546 (void) mb_put_mem(out_mb, &hdr.h_id, ID_SZ, MB_MSYSTEM);
547 (void) mb_put_uint32le(out_mb, hdr.h_type);
549 (void) mb_put_sb_hdr(out_mb, &hdr.h_lm_resp);
550 (void) mb_put_sb_hdr(out_mb, &hdr.h_nt_resp);
552 (void) mb_put_sb_hdr(out_mb, &hdr.h_domain);
553 (void) mb_put_sb_hdr(out_mb, &hdr.h_user);
554 (void) mb_put_sb_hdr(out_mb, &hdr.h_wksta);
556 (void) mb_put_sb_hdr(out_mb, &hdr.h_ssn_key);
557 (void) mb_put_uint32le(out_mb, hdr.h_flags);
559 /* Put zeros for the MIC - filled in later */
560 pmic = mb_reserve(out_mb, NTLM_HASH_SZ);
562 /* Put the payload. */
563 err = mb_put_mbuf(out_mb, mb2.mb_top);
564 mb2.mb_top = NULL; /* consumed */
567 * Compute the MIC and stuff that in...
568 * The MIC is apparently optional.
570 (void) pmic;
572 out:
573 mb_done(&mb2);
574 mb_done(&lm_mbc);
575 mb_done(&nt_mbc);
576 mb_done(&ti_mbc);
577 mb_done(&ek_mbc);
579 return (err);
583 * Helper for ntlmssp_put_type3 when doing key exchange.
585 * "ExportedSessionKey" is what we give to the "application"
586 * layer, which in here means the MAC key for SMB signing.
587 * With "key exchange", we replace the ExportedSessionKey
588 * with random data and send that (encrypted) to the peer.
590 static int
591 ntlm_rand_ssn_key(
592 struct smb_ctx *ctx,
593 ntlmssp_state_t *ssp_st,
594 struct mbdata *ek_mbp)
597 uchar_t *encr_ssn_key;
598 int err;
600 if ((err = mb_init(ek_mbp)) != 0)
601 return (err);
602 encr_ssn_key = mb_reserve(ek_mbp, NTLM_HASH_SZ);
604 /* Set "ExportedSessionKey to NONCE(16) */
605 (void) smb_get_urandom(ctx->ct_ssn_key, NTLM_HASH_SZ);
607 /* Set "EncryptedRandomSessionKey" to RC4(...) */
608 err = smb_encrypt_RC4(encr_ssn_key, NTLM_HASH_SZ,
609 ssp_st->ss_kxkey, NTLM_HASH_SZ,
610 ctx->ct_ssn_key, NTLM_HASH_SZ);
612 return (err);
616 * ntlmssp_final
618 * Called after successful authentication.
619 * Setup the MAC key for signing.
622 ntlmssp_final(struct ssp_ctx *sp)
624 struct smb_ctx *ctx = sp->smb_ctx;
625 int err = 0;
628 * MAC_key is just the session key, but
629 * Only on the first successful auth.
631 if ((ctx->ct_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) &&
632 (ctx->ct_mackey == NULL)) {
633 ctx->ct_mackeylen = NTLM_HASH_SZ;
634 ctx->ct_mackey = malloc(ctx->ct_mackeylen);
635 if (ctx->ct_mackey == NULL) {
636 ctx->ct_mackeylen = 0;
637 err = ENOMEM;
638 goto out;
640 memcpy(ctx->ct_mackey, ctx->ct_ssn_key, NTLM_HASH_SZ);
642 * Apparently, the server used seq. no. zero
643 * for our previous message, so next is two.
645 ctx->ct_mac_seqno = 2;
648 out:
649 return (err);
653 * ntlmssp_next_token
655 * See ssp.c: ssp_ctx_next_token
658 ntlmssp_next_token(struct ssp_ctx *sp, struct mbdata *in_mb,
659 struct mbdata *out_mb)
661 int err;
663 if (out_mb == NULL) {
664 /* final call on successful auth. */
665 err = ntlmssp_final(sp);
666 goto out;
669 /* Will build an ouptut token. */
670 err = mb_init(out_mb);
671 if (err)
672 goto out;
675 * When called with in_mb == NULL, it means
676 * this is the first call for this session,
677 * so put a Type 1 (initialize) token.
679 if (in_mb == NULL) {
680 err = ntlmssp_put_type1(sp, out_mb);
681 goto out;
685 * This is not the first call, so
686 * parse the response token we received.
687 * It should be a Type 2 (challenge).
688 * Then put a Type 3 (authenticate)
690 err = ntlmssp_get_type2(sp, in_mb);
691 if (err)
692 goto out;
694 err = ntlmssp_put_type3(sp, out_mb);
696 out:
697 if (err)
698 DPRINT("ret: %d", err);
699 return (err);
703 * ntlmssp_ctx_destroy
705 * Destroy mechanism-specific data.
707 void
708 ntlmssp_destroy(struct ssp_ctx *sp)
710 ntlmssp_state_t *ssp_st;
712 ssp_st = sp->sp_private;
713 if (ssp_st != NULL) {
714 sp->sp_private = NULL;
715 free(ssp_st->ss_target_name);
716 m_freem(ssp_st->ss_target_info);
717 free(ssp_st);
722 * ntlmssp_init_clnt
724 * Initialize a new NTLMSSP client context.
727 ntlmssp_init_client(struct ssp_ctx *sp)
729 ntlmssp_state_t *ssp_st;
731 if ((sp->smb_ctx->ct_authflags &
732 (SMB_AT_NTLM2 | SMB_AT_NTLM1 | SMB_AT_ANON)) == 0) {
733 DPRINT("No NTLM authflags");
734 return (EINVAL);
737 ssp_st = calloc(1, sizeof (*ssp_st));
738 if (ssp_st == NULL)
739 return (ENOMEM);
741 sp->sp_nexttok = ntlmssp_next_token;
742 sp->sp_destroy = ntlmssp_destroy;
743 sp->sp_private = ssp_st;
745 return (0);