1 /*
2 * CDDL HEADER START
3 *
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.
7 *
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.
12 *
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]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 */
26
27 /*
28 * Security Provider glue
29 *
30 * Modeled after SSPI for now, only because we're currently
31 * using the Microsoft sample spnego code.
32 *
33 * ToDo: Port all of this to GSS-API plugins.
34 */
35
36 #include <errno.h>
37 #include <stdio.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>
45
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>
51
52 #include <netinet/in.h>
53 #include <netinet/tcp.h>
54 #include <arpa/inet.h>
55
56 #include <netsmb/smb_lib.h>
57 #include <netsmb/mchain.h>
58
59 #include "private.h"
60 #include "charsets.h"
61 #include "spnego.h"
62 #include "derparse.h"
63 #include "ssp.h"
64
65
66 /*
67 * ssp_ctx_create_client
68 *
69 * This is the first function called for SMB "extended security".
70 * Here we select a security support provider (SSP), or mechanism,
71 * and build the security context used throughout authentication.
72 *
73 * Note that we receive a "hint" in the SMB Negotiate response
74 * that contains the list of mechanisms supported by the server.
75 * We use this to help us select a mechanism.
76 *
77 * With SSPI this would call:
78 * ssp->InitSecurityInterface()
79 * ssp->AcquireCredentialsHandle()
80 * ssp->InitializeSecurityContext()
81 * With GSS-API this will become:
82 * gss_import_name(... service_principal_name)
83 * gss_init_sec_context(), etc.
84 */
85 int
ssp_ctx_create_client(struct smb_ctx * ctx,struct mbdata * hint_mb)86 ssp_ctx_create_client(struct smb_ctx *ctx, struct mbdata *hint_mb)
87 {
88 struct ssp_ctx *sp;
89 mbuf_t *m;
90 SPNEGO_MECH_OID oid;
91 int indx, rc;
92 int err = ENOTSUP; /* in case nothing matches */
93
94 sp = malloc(sizeof (*sp));
95 if (sp == NULL)
96 return (ENOMEM);
97 bzero(sp, sizeof (*sp));
98 ctx->ct_ssp_ctx = sp;
99 sp->smb_ctx = ctx;
100
101 /*
102 * Parse the SPNEGO "hint" to get the server's list of
103 * supported mechanisms. If the "hint" is empty,
104 * assume NTLMSSP. (Or could use "raw NTLMSSP")
105 */
106 m = hint_mb->mb_top;
107 if (m == NULL)
108 goto use_ntlm;
109 rc = spnegoInitFromBinary((uchar_t *)m->m_data, m->m_len,
110 &sp->sp_hint);
111 if (rc) {
112 DPRINT("parse hint, rc %d", rc);
113 goto use_ntlm;
114 }
115
116 /*
117 * Did the server offer Kerberos?
118 * Either spec. OID or legacy is OK,
119 * but have to remember what we got.
120 */
121 oid = spnego_mech_oid_NotUsed;
122 if (0 == spnegoIsMechTypeAvailable(sp->sp_hint,
123 spnego_mech_oid_Kerberos_V5, &indx))
124 oid = spnego_mech_oid_Kerberos_V5;
125 else if (0 == spnegoIsMechTypeAvailable(sp->sp_hint,
126 spnego_mech_oid_Kerberos_V5_Legacy, &indx))
127 oid = spnego_mech_oid_Kerberos_V5_Legacy;
128 if (oid != spnego_mech_oid_NotUsed) {
129 /*
130 * Yes! Server offers Kerberos.
131 * Try to init our krb5 mechanism.
132 * It will fail if the calling user
133 * does not have krb5 credentials.
134 */
135 sp->sp_mech = oid;
136 err = krb5ssp_init_client(sp);
137 if (err == 0) {
138 DPRINT("using Kerberos");
139 return (0);
140 }
141 /* else fall back to NTLMSSP */
142 }
143
144 /*
145 * Did the server offer NTLMSSP?
146 */
147 if (0 == spnegoIsMechTypeAvailable(sp->sp_hint,
148 spnego_mech_oid_NTLMSSP, &indx)) {
149 /*
150 * OK, we'll use NTLMSSP
151 */
152 use_ntlm:
153 sp->sp_mech = spnego_mech_oid_NTLMSSP;
154 err = ntlmssp_init_client(sp);
155 if (err == 0) {
156 DPRINT("using NTLMSSP");
157 return (0);
158 }
159 }
160
161 /* No supported mechanisms! */
162 return (err);
163 }
164
165
166 /*
167 * ssp_ctx_destroy
168 *
169 * Dispatch to the mechanism-specific destroy.
170 */
171 void
ssp_ctx_destroy(struct smb_ctx * ctx)172 ssp_ctx_destroy(struct smb_ctx *ctx)
173 {
174 ssp_ctx_t *sp;
175
176 sp = ctx->ct_ssp_ctx;
177 ctx->ct_ssp_ctx = NULL;
178
179 if (sp == NULL)
180 return;
181
182 if (sp->sp_destroy != NULL)
183 (sp->sp_destroy)(sp);
184
185 if (sp->sp_hint != NULL)
186 spnegoFreeData(sp->sp_hint);
187
188 free(sp);
189 }
190
191
192 /*
193 * ssp_ctx_next_token
194 *
195 * This is the function called to generate the next token to send,
196 * given a token just received, using the selected back-end method.
197 * The back-end method is called a security service provider (SSP).
198 *
199 * This is also called to generate the first token to send
200 * (when called with caller_in == NULL) and to handle the last
201 * token received (when called with caller_out == NULL).
202 * See caller: smb_ssnsetup_spnego
203 *
204 * Note that if the back-end SSP "next token" function ever
205 * returns an error, the conversation ends, and there are
206 * no further calls to this function for this context.
207 *
208 * General outline of this funcion:
209 * if (caller_in)
210 * Unwrap caller_in spnego blob,
211 * store payload in body_in
212 * Call back-end SSP "next token" method (body_in, body_out)
213 * if (caller_out)
214 * Wrap returned body_out in spnego,
215 * store in caller_out
216 *
217 * With SSPI this would call:
218 * ssp->InitializeSecurityContext()
219 * With GSS-API this will become:
220 * gss_init_sec_context()
221 */
222 int
ssp_ctx_next_token(struct smb_ctx * ctx,struct mbdata * caller_in,struct mbdata * caller_out)223 ssp_ctx_next_token(struct smb_ctx *ctx,
224 struct mbdata *caller_in,
225 struct mbdata *caller_out)
226 {
227 struct mbdata body_in, body_out;
228 SPNEGO_TOKEN_HANDLE stok_in, stok_out;
229 SPNEGO_NEGRESULT result;
230 ssp_ctx_t *sp;
231 struct mbuf *m;
232 ulong_t toklen;
233 int err, rc;
234
235 bzero(&body_in, sizeof (body_in));
236 bzero(&body_out, sizeof (body_out));
237 stok_out = stok_in = NULL;
238 sp = ctx->ct_ssp_ctx;
239
240 /*
241 * If we have an spnego input token, parse it,
242 * extract the payload for the back-end SSP.
243 */
244 if (caller_in != NULL) {
245
246 /*
247 * Let the spnego code parse it.
248 */
249 m = caller_in->mb_top;
250 rc = spnegoInitFromBinary((uchar_t *)m->m_data,
251 m->m_len, &stok_in);
252 if (rc) {
253 DPRINT("parse reply, rc %d", rc);
254 err = EBADRPC;
255 goto out;
256 }
257 /* Note: Allocated stok_in */
258
259 /*
260 * Now get the payload. Two calls:
261 * first gets the size, 2nd the data.
262 *
263 * Expect SPNEGO_E_BUFFER_TOO_SMALL here,
264 * but if the payload is missing, we'll
265 * get SPNEGO_E_ELEMENT_UNAVAILABLE.
266 */
267 rc = spnegoGetMechToken(stok_in, NULL, &toklen);
268 switch (rc) {
269 case SPNEGO_E_ELEMENT_UNAVAILABLE:
270 toklen = 0;
271 break;
272 case SPNEGO_E_BUFFER_TOO_SMALL:
273 /* have toklen */
274 break;
275 default:
276 DPRINT("GetMechTok1, rc %d", rc);
277 err = EBADRPC;
278 goto out;
279 }
280 err = mb_init_sz(&body_in, (size_t)toklen);
281 if (err)
282 goto out;
283 m = body_in.mb_top;
284 if (toklen > 0) {
285 rc = spnegoGetMechToken(stok_in,
286 (uchar_t *)m->m_data, &toklen);
287 if (rc) {
288 DPRINT("GetMechTok2, rc %d", rc);
289 err = EBADRPC;
290 goto out;
291 }
292 body_in.mb_count = m->m_len = (size_t)toklen;
293 }
294 }
295
296 /*
297 * Call the back-end security provider (SSP) to
298 * handle the received token (if present) and
299 * generate an output token (if requested).
300 */
301 err = sp->sp_nexttok(sp,
302 caller_in ? &body_in : NULL,
303 caller_out ? &body_out : NULL);
304 if (err)
305 goto out;
306
307 /*
308 * Wrap the outgoing body if requested,
309 * either negTokenInit on first call, or
310 * negTokenTarg on subsequent calls.
311 */
312 if (caller_out != NULL) {
313 m = body_out.mb_top;
314
315 if (caller_in == NULL) {
316 /*
317 * This is the first call, so create a
318 * negTokenInit.
319 */
320 rc = spnegoCreateNegTokenInit(
321 sp->sp_mech, 0,
322 (uchar_t *)m->m_data, m->m_len,
323 NULL, 0, &stok_out);
324 /* Note: allocated stok_out */
325 } else {
326 /*
327 * Note: must pass spnego_mech_oid_NotUsed,
328 * instead of sp->sp_mech so that the spnego
329 * code will not marshal a mech OID list.
330 * The mechanism is determined at this point,
331 * and some servers won't parse an unexpected
332 * mech. OID list in a negTokenTarg
333 */
334 rc = spnegoCreateNegTokenTarg(
335 spnego_mech_oid_NotUsed,
336 spnego_negresult_NotUsed,
337 (uchar_t *)m->m_data, m->m_len,
338 NULL, 0, &stok_out);
339 /* Note: allocated stok_out */
340 }
341 if (rc) {
342 DPRINT("CreateNegTokenX, rc 0x%x", rc);
343 err = EBADRPC;
344 goto out;
345 }
346
347 /*
348 * Copy binary from stok_out to caller_out
349 * Two calls: get the size, get the data.
350 */
351 rc = spnegoTokenGetBinary(stok_out, NULL, &toklen);
352 if (rc != SPNEGO_E_BUFFER_TOO_SMALL) {
353 DPRINT("GetBinary1, rc 0x%x", rc);
354 err = EBADRPC;
355 goto out;
356 }
357 err = mb_init_sz(caller_out, (size_t)toklen);
358 if (err)
359 goto out;
360 m = caller_out->mb_top;
361 rc = spnegoTokenGetBinary(stok_out,
362 (uchar_t *)m->m_data, &toklen);
363 if (rc) {
364 DPRINT("GetBinary2, rc 0x%x", rc);
365 err = EBADRPC;
366 goto out;
367 }
368 caller_out->mb_count = m->m_len = (size_t)toklen;
369 } else {
370 /*
371 * caller_out == NULL, so this is the "final" call.
372 * Get final SPNEGO result from the INPUT token.
373 */
374 rc = spnegoGetNegotiationResult(stok_in, &result);
375 if (rc) {
376 DPRINT("rc 0x%x", rc);
377 err = EBADRPC;
378 goto out;
379 }
380 DPRINT("spnego result: 0x%x", result);
381 if (result != spnego_negresult_success) {
382 err = EAUTH;
383 goto out;
384 }
385 }
386 err = 0;
387
388 out:
389 mb_done(&body_in);
390 mb_done(&body_out);
391 spnegoFreeData(stok_in);
392 spnegoFreeData(stok_out);
393
394 return (err);
395 }
396