Print this page
NEX-16824 SMB client connection setup rework
NEX-17232 SMB client reconnect failures
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
and: (improve debug)
| Split |
Close |
| Expand all |
| Collapse all |
--- old/usr/src/lib/libsmbfs/smb/krb5ssp.c
+++ new/usr/src/lib/libsmbfs/smb/krb5ssp.c
1 1 /*
2 2 * Copyright (c) 2000, Boris Popov
3 3 * All rights reserved.
4 4 *
5 5 * Redistribution and use in source and binary forms, with or without
6 6 * modification, are permitted provided that the following conditions
7 7 * are met:
8 8 * 1. Redistributions of source code must retain the above copyright
9 9 * notice, this list of conditions and the following disclaimer.
10 10 * 2. Redistributions in binary form must reproduce the above copyright
11 11 * notice, this list of conditions and the following disclaimer in the
12 12 * documentation and/or other materials provided with the distribution.
13 13 * 3. All advertising materials mentioning features or use of this software
14 14 * must display the following acknowledgement:
15 15 * This product includes software developed by Boris Popov.
16 16 * 4. Neither the name of the author nor the names of any co-contributors
17 17 * may be used to endorse or promote products derived from this software
18 18 * without specific prior written permission.
19 19 *
20 20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
↓ open down ↓ |
24 lines elided |
↑ open up ↑ |
25 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 30 * SUCH DAMAGE.
31 31 */
32 32
33 33 /*
34 34 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
35 + * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
35 36 */
36 37
37 38 /*
38 39 * Kerberos V Security Support Provider
39 40 *
40 41 * Based on code previously in ctx.c (from Boris Popov?)
41 42 * but then mostly rewritten at Sun.
42 43 */
43 44
44 45 #include <errno.h>
45 46 #include <stdio.h>
46 47 #include <stddef.h>
47 48 #include <stdlib.h>
48 49 #include <unistd.h>
49 50 #include <strings.h>
50 51 #include <netdb.h>
51 52 #include <libintl.h>
52 53 #include <xti.h>
53 54 #include <assert.h>
54 55
55 56 #include <sys/types.h>
56 57 #include <sys/time.h>
57 58 #include <sys/byteorder.h>
58 59 #include <sys/socket.h>
59 60 #include <sys/fcntl.h>
60 61
61 62 #include <netinet/in.h>
62 63 #include <netinet/tcp.h>
63 64 #include <arpa/inet.h>
64 65
65 66 #include <netsmb/smb.h>
66 67 #include <netsmb/smb_lib.h>
67 68 #include <netsmb/mchain.h>
68 69
69 70 #include "private.h"
70 71 #include "charsets.h"
71 72 #include "spnego.h"
72 73 #include "derparse.h"
73 74 #include "ssp.h"
74 75
75 76 #include <kerberosv5/krb5.h>
76 77 #include <kerberosv5/com_err.h>
77 78 #include <gssapi/gssapi.h>
78 79 #include <gssapi/mechs/krb5/include/auth_con.h>
79 80
80 81 /* RFC 4121 checksum type ID. */
81 82 #define CKSUM_TYPE_RFC4121 0x8003
82 83
83 84 /* RFC 1964 token ID codes */
84 85 #define KRB_AP_REQ 1
85 86 #define KRB_AP_REP 2
86 87 #define KRB_ERROR 3
87 88
88 89 extern MECH_OID g_stcMechOIDList [];
89 90
90 91 typedef struct krb5ssp_state {
91 92 /* Filled in by krb5ssp_init_client */
92 93 krb5_context ss_krb5ctx; /* krb5 context (ptr) */
93 94 krb5_ccache ss_krb5cc; /* credentials cache (ptr) */
94 95 krb5_principal ss_krb5clp; /* client principal (ptr) */
95 96 /* Filled in by krb5ssp_get_tkt */
96 97 krb5_auth_context ss_auth; /* auth ctx. w/ server (ptr) */
97 98 } krb5ssp_state_t;
98 99
99 100
100 101 /*
101 102 * adds a GSSAPI wrapper
102 103 */
103 104 int
104 105 krb5ssp_tkt2gtok(uchar_t *tkt, ulong_t tktlen,
105 106 uchar_t **gtokp, ulong_t *gtoklenp)
106 107 {
107 108 ulong_t len;
108 109 ulong_t bloblen = tktlen;
109 110 uchar_t krbapreq[2] = { KRB_AP_REQ, 0 };
110 111 uchar_t *blob = NULL; /* result */
111 112 uchar_t *b;
112 113
113 114 bloblen += sizeof (krbapreq);
114 115 bloblen += g_stcMechOIDList[spnego_mech_oid_Kerberos_V5].iLen;
115 116 len = bloblen;
116 117 bloblen = ASNDerCalcTokenLength(bloblen, bloblen);
117 118 if ((blob = malloc(bloblen)) == NULL) {
118 119 DPRINT("malloc");
119 120 return (ENOMEM);
120 121 }
121 122
122 123 b = blob;
123 124 b += ASNDerWriteToken(b, SPNEGO_NEGINIT_APP_CONSTRUCT, NULL, len);
124 125 b += ASNDerWriteOID(b, spnego_mech_oid_Kerberos_V5);
125 126 memcpy(b, krbapreq, sizeof (krbapreq));
126 127 b += sizeof (krbapreq);
127 128
128 129 assert(b + tktlen == blob + bloblen);
129 130 memcpy(b, tkt, tktlen);
130 131 *gtoklenp = bloblen;
131 132 *gtokp = blob;
132 133 return (0);
133 134 }
134 135
135 136 /*
136 137 * See "Windows 2000 Kerberos Interoperability" paper by
137 138 * Christopher Nebergall. RC4 HMAC is the W2K default but
138 139 * Samba support lagged (not due to Samba itself, but due to OS'
139 140 * Kerberos implementations.)
140 141 *
141 142 * Only session enc type should matter, not ticket enc type,
142 143 * per Sam Hartman on krbdev.
143 144 *
144 145 * Preauthentication failure topics in krb-protocol may help here...
145 146 * try "John Brezak" and/or "Clifford Neuman" too.
146 147 */
147 148 static krb5_enctype kenctypes[] = {
148 149 ENCTYPE_ARCFOUR_HMAC, /* defined in krb5.h */
149 150 ENCTYPE_DES_CBC_MD5,
150 151 ENCTYPE_DES_CBC_CRC,
151 152 ENCTYPE_NULL
152 153 };
153 154
154 155 static const int rq_opts =
155 156 AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED;
156 157
157 158 /*
158 159 * Obtain a kerberos ticket for the host we're connecting to.
159 160 * (This does the KRB_TGS exchange.)
160 161 */
161 162 static int
162 163 krb5ssp_get_tkt(krb5ssp_state_t *ss, char *server,
163 164 uchar_t **tktp, ulong_t *tktlenp)
164 165 {
165 166 krb5_context kctx = ss->ss_krb5ctx;
166 167 krb5_ccache kcc = ss->ss_krb5cc;
167 168 krb5_data indata = {0};
168 169 krb5_data outdata = {0};
169 170 krb5_error_code kerr = 0;
170 171 const char *fn = NULL;
171 172 uchar_t *tkt;
172 173
173 174 /* Should have these from krb5ssp_init_client. */
174 175 if (kctx == NULL || kcc == NULL) {
175 176 fn = "null kctx or kcc";
176 177 kerr = EINVAL;
177 178 goto out;
178 179 }
179 180
180 181 kerr = krb5_set_default_tgs_enctypes(kctx, kenctypes);
181 182 if (kerr != 0) {
182 183 fn = "krb5_set_default_tgs_enctypes";
183 184 goto out;
184 185 }
185 186
186 187 /* Get ss_auth now so we can set req_chsumtype. */
187 188 kerr = krb5_auth_con_init(kctx, &ss->ss_auth);
188 189 if (kerr != 0) {
189 190 fn = "krb5_auth_con_init";
190 191 goto out;
191 192 }
192 193 /* Missing krb5_auth_con_set_req_cksumtype(), so inline. */
193 194 ss->ss_auth->req_cksumtype = CKSUM_TYPE_RFC4121;
194 195
195 196 /*
196 197 * Build an RFC 4121 "checksum" with NULL channel bindings,
197 198 * like make_gss_checksum(). Numbers here from the RFC.
198 199 */
199 200 indata.length = 24;
200 201 if ((indata.data = calloc(1, indata.length)) == NULL) {
201 202 kerr = ENOMEM;
202 203 fn = "malloc checksum";
203 204 goto out;
204 205 }
205 206 indata.data[0] = 16; /* length of "Bnd" field. */
206 207 indata.data[20] = GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG;
207 208 /* Done building the "checksum". */
208 209
209 210 kerr = krb5_mk_req(kctx, &ss->ss_auth, rq_opts, "cifs", server,
210 211 &indata, kcc, &outdata);
211 212 if (kerr != 0) {
212 213 fn = "krb5_mk_req";
213 214 goto out;
214 215 }
215 216 if ((tkt = malloc(outdata.length)) == NULL) {
216 217 kerr = ENOMEM;
217 218 fn = "malloc signing key";
218 219 goto out;
219 220 }
220 221 memcpy(tkt, outdata.data, outdata.length);
221 222 *tktp = tkt;
222 223 *tktlenp = outdata.length;
223 224 kerr = 0;
224 225
225 226 out:
226 227 if (kerr) {
227 228 if (fn == NULL)
228 229 fn = "?";
229 230 DPRINT("%s err 0x%x: %s", fn, kerr, error_message(kerr));
230 231 if (kerr <= 0 || kerr > ESTALE)
231 232 kerr = EAUTH;
232 233 }
233 234
234 235 if (outdata.data)
235 236 krb5_free_data_contents(kctx, &outdata);
236 237
237 238 if (indata.data)
238 239 free(indata.data);
239 240
240 241 /* Free kctx in krb5ssp_destroy */
241 242 return (kerr);
242 243 }
243 244
244 245
245 246 /*
246 247 * Build an RFC 1964 KRB_AP_REQ message
247 248 * The caller puts on the SPNEGO wrapper.
248 249 */
249 250 int
250 251 krb5ssp_put_request(struct ssp_ctx *sp, struct mbdata *out_mb)
251 252 {
252 253 int err;
253 254 struct smb_ctx *ctx = sp->smb_ctx;
254 255 krb5ssp_state_t *ss = sp->sp_private;
255 256 uchar_t *tkt = NULL;
256 257 ulong_t tktlen;
257 258 uchar_t *gtok = NULL; /* gssapi token */
258 259 ulong_t gtoklen; /* gssapi token length */
259 260 char *prin = ctx->ct_srvname;
260 261
|
↓ open down ↓ |
216 lines elided |
↑ open up ↑ |
261 262 if ((err = krb5ssp_get_tkt(ss, prin, &tkt, &tktlen)) != 0)
262 263 goto out;
263 264 if ((err = krb5ssp_tkt2gtok(tkt, tktlen, >ok, >oklen)) != 0)
264 265 goto out;
265 266
266 267 if ((err = mb_init_sz(out_mb, gtoklen)) != 0)
267 268 goto out;
268 269 if ((err = mb_put_mem(out_mb, gtok, gtoklen, MB_MSYSTEM)) != 0)
269 270 goto out;
270 271
271 - if (ctx->ct_vcflags & SMBV_WILL_SIGN)
272 - ctx->ct_hflags2 |= SMB_FLAGS2_SECURITY_SIGNATURE;
273 -
274 272 out:
275 273 if (gtok)
276 274 free(gtok);
277 275 if (tkt)
278 276 free(tkt);
279 277
280 278 return (err);
281 279 }
282 280
283 281 /*
284 282 * Unwrap a GSS-API encapsulated RFC 1964 reply message,
285 283 * i.e. type KRB_AP_REP or KRB_ERROR.
286 284 */
287 285 int
288 286 krb5ssp_get_reply(struct ssp_ctx *sp, struct mbdata *in_mb)
289 287 {
290 288 krb5ssp_state_t *ss = sp->sp_private;
291 289 mbuf_t *m = in_mb->mb_top;
292 290 int err = EBADRPC;
293 291 int dlen, rc;
294 292 long actual_len, token_len;
295 293 uchar_t *data;
296 294 krb5_data ap = {0};
297 295 krb5_ap_rep_enc_part *reply = NULL;
298 296
299 297 /* cheating: this mbuf is contiguous */
300 298 assert(m->m_data == in_mb->mb_pos);
301 299 data = (uchar_t *)m->m_data;
302 300 dlen = m->m_len;
303 301
304 302 /*
305 303 * Peel off the GSS-API wrapper. Looks like:
306 304 * AppToken: 60 81 83
307 305 * OID(KRB5): 06 09 2a 86 48 86 f7 12 01 02 02
308 306 * KRB_AP_REP: 02 00
309 307 */
310 308 rc = ASNDerCheckToken(data, SPNEGO_NEGINIT_APP_CONSTRUCT,
311 309 0, dlen, &token_len, &actual_len);
312 310 if (rc != SPNEGO_E_SUCCESS) {
313 311 DPRINT("no AppToken? rc=0x%x", rc);
314 312 goto out;
315 313 }
316 314 if (dlen < actual_len)
317 315 goto out;
318 316 data += actual_len;
319 317 dlen -= actual_len;
320 318
321 319 /* OID (KRB5) */
322 320 rc = ASNDerCheckOID(data, spnego_mech_oid_Kerberos_V5,
323 321 dlen, &actual_len);
324 322 if (rc != SPNEGO_E_SUCCESS) {
325 323 DPRINT("no OID? rc=0x%x", rc);
326 324 goto out;
327 325 }
328 326 if (dlen < actual_len)
329 327 goto out;
330 328 data += actual_len;
331 329 dlen -= actual_len;
332 330
333 331 /* KRB_AP_REP or KRB_ERROR */
334 332 if (data[0] != KRB_AP_REP) {
335 333 DPRINT("KRB5 type: %d", data[1]);
336 334 goto out;
337 335 }
338 336 if (dlen < 2)
339 337 goto out;
340 338 data += 2;
341 339 dlen -= 2;
342 340
343 341 /*
344 342 * Now what's left should be a krb5 reply
345 343 * NB: ap is NOT allocated, so don't free it.
346 344 */
347 345 ap.length = dlen;
348 346 ap.data = (char *)data;
349 347 rc = krb5_rd_rep(ss->ss_krb5ctx, ss->ss_auth, &ap, &reply);
350 348 if (rc != 0) {
351 349 DPRINT("krb5_rd_rep: err 0x%x (%s)",
352 350 rc, error_message(rc));
353 351 err = EAUTH;
354 352 goto out;
355 353 }
356 354
357 355 /*
358 356 * Have the decoded reply. Save anything?
359 357 *
360 358 * NB: If this returns an error, we will get
361 359 * no more calls into this back-end module.
362 360 */
363 361 err = 0;
364 362
365 363 out:
366 364 if (reply != NULL)
367 365 krb5_free_ap_rep_enc_part(ss->ss_krb5ctx, reply);
368 366 if (err)
369 367 DPRINT("ret %d", err);
370 368
371 369 return (err);
372 370 }
373 371
374 372 /*
375 373 * krb5ssp_final
|
↓ open down ↓ |
92 lines elided |
↑ open up ↑ |
376 374 *
377 375 * Called after successful authentication.
378 376 * Setup the MAC key for signing.
379 377 */
380 378 int
381 379 krb5ssp_final(struct ssp_ctx *sp)
382 380 {
383 381 struct smb_ctx *ctx = sp->smb_ctx;
384 382 krb5ssp_state_t *ss = sp->sp_private;
385 383 krb5_keyblock *ssn_key = NULL;
386 - int err, len;
384 + int err;
387 385
388 386 /*
389 387 * Save the session key, used for SMB signing
390 388 * and possibly other consumers (RPC).
391 389 */
392 390 err = krb5_auth_con_getlocalsubkey(
393 391 ss->ss_krb5ctx, ss->ss_auth, &ssn_key);
394 392 if (err != 0) {
395 393 DPRINT("_getlocalsubkey, err=0x%x (%s)",
396 394 err, error_message(err));
397 395 if (err <= 0 || err > ESTALE)
398 396 err = EAUTH;
399 397 goto out;
400 398 }
401 - memset(ctx->ct_ssn_key, 0, SMBIOC_HASH_SZ);
402 - if ((len = ssn_key->length) > SMBIOC_HASH_SZ)
403 - len = SMBIOC_HASH_SZ;
404 - memcpy(ctx->ct_ssn_key, ssn_key->contents, len);
405 399
400 + /* Sanity check the length */
401 + if (ssn_key->length > 1024) {
402 + DPRINT("session key too long");
403 + err = EAUTH;
404 + goto out;
405 + }
406 +
406 407 /*
407 - * Set the MAC key on the first successful auth.
408 + * Update/save the session key.
408 409 */
409 - if ((ctx->ct_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) &&
410 - (ctx->ct_mackey == NULL)) {
411 - ctx->ct_mackeylen = ssn_key->length;
412 - ctx->ct_mackey = malloc(ctx->ct_mackeylen);
413 - if (ctx->ct_mackey == NULL) {
414 - ctx->ct_mackeylen = 0;
415 - err = ENOMEM;
416 - goto out;
417 - }
418 - memcpy(ctx->ct_mackey, ssn_key->contents,
419 - ctx->ct_mackeylen);
420 - /*
421 - * Apparently, the server used seq. no. zero
422 - * for our previous message, so next is two.
423 - */
424 - ctx->ct_mac_seqno = 2;
410 + if (ctx->ct_ssnkey_buf != NULL) {
411 + free(ctx->ct_ssnkey_buf);
412 + ctx->ct_ssnkey_buf = NULL;
425 413 }
414 + ctx->ct_ssnkey_buf = malloc(ssn_key->length);
415 + if (ctx->ct_ssnkey_buf == NULL) {
416 + err = ENOMEM;
417 + goto out;
418 + }
419 + ctx->ct_ssnkey_len = ssn_key->length;
420 + memcpy(ctx->ct_ssnkey_buf, ssn_key->contents, ctx->ct_ssnkey_len);
426 421 err = 0;
427 422
428 423 out:
429 - if (ssn_key)
424 + if (ssn_key != NULL)
430 425 krb5_free_keyblock(ss->ss_krb5ctx, ssn_key);
431 426
432 427 return (err);
433 428 }
434 429
435 430 /*
436 431 * krb5ssp_next_token
437 432 *
438 433 * See ssp.c: ssp_ctx_next_token
439 434 */
440 435 int
441 436 krb5ssp_next_token(struct ssp_ctx *sp, struct mbdata *in_mb,
442 437 struct mbdata *out_mb)
443 438 {
444 439 int err;
445 440
446 441 /*
447 442 * Note: in_mb == NULL on the first call.
448 443 */
449 444 if (in_mb) {
450 445 err = krb5ssp_get_reply(sp, in_mb);
451 446 if (err)
452 447 goto out;
453 448 }
454 449
455 450 if (out_mb) {
456 451 err = krb5ssp_put_request(sp, out_mb);
457 452 } else
458 453 err = krb5ssp_final(sp);
459 454
460 455 out:
461 456 if (err)
462 457 DPRINT("ret: %d", err);
463 458 return (err);
464 459 }
465 460
466 461 /*
467 462 * krb5ssp_ctx_destroy
468 463 *
469 464 * Destroy mechanism-specific data.
470 465 */
471 466 void
472 467 krb5ssp_destroy(struct ssp_ctx *sp)
473 468 {
474 469 krb5ssp_state_t *ss;
475 470 krb5_context kctx;
476 471
477 472 ss = sp->sp_private;
478 473 if (ss == NULL)
479 474 return;
480 475 sp->sp_private = NULL;
481 476
482 477 if ((kctx = ss->ss_krb5ctx) != NULL) {
483 478 /* from krb5ssp_get_tkt */
484 479 if (ss->ss_auth)
485 480 (void) krb5_auth_con_free(kctx, ss->ss_auth);
486 481 /* from krb5ssp_init_client */
487 482 if (ss->ss_krb5clp)
488 483 krb5_free_principal(kctx, ss->ss_krb5clp);
489 484 if (ss->ss_krb5cc)
490 485 (void) krb5_cc_close(kctx, ss->ss_krb5cc);
491 486 krb5_free_context(kctx);
492 487 }
493 488
494 489 free(ss);
495 490 }
496 491
497 492 /*
498 493 * krb5ssp_init_clnt
499 494 *
500 495 * Initialize a new Kerberos SSP client context.
501 496 *
502 497 * The user must already have a TGT in their credential cache,
503 498 * as shown by the "klist" command.
504 499 */
505 500 int
506 501 krb5ssp_init_client(struct ssp_ctx *sp)
507 502 {
508 503 krb5ssp_state_t *ss;
509 504 krb5_error_code kerr;
510 505 krb5_context kctx = NULL;
511 506 krb5_ccache kcc = NULL;
512 507 krb5_principal kprin = NULL;
513 508
514 509 if ((sp->smb_ctx->ct_authflags & SMB_AT_KRB5) == 0) {
515 510 DPRINT("KRB5 not in authflags");
516 511 return (ENOTSUP);
517 512 }
518 513
519 514 ss = calloc(1, sizeof (*ss));
520 515 if (ss == NULL)
521 516 return (ENOMEM);
522 517
523 518 sp->sp_nexttok = krb5ssp_next_token;
524 519 sp->sp_destroy = krb5ssp_destroy;
525 520 sp->sp_private = ss;
526 521
527 522 kerr = krb5_init_context(&kctx);
528 523 if (kerr) {
529 524 DPRINT("krb5_init_context, kerr 0x%x", kerr);
530 525 goto errout;
531 526 }
532 527 ss->ss_krb5ctx = kctx;
533 528
534 529 /* non-default would instead use krb5_cc_resolve */
535 530 kerr = krb5_cc_default(kctx, &kcc);
536 531 if (kerr) {
537 532 DPRINT("krb5_cc_default, kerr 0x%x", kerr);
538 533 goto errout;
539 534 }
540 535 ss->ss_krb5cc = kcc;
541 536
542 537 /*
543 538 * Get the client principal (ticket),
544 539 * or discover that we don't have one.
545 540 */
546 541 kerr = krb5_cc_get_principal(kctx, kcc, &kprin);
547 542 if (kerr) {
548 543 DPRINT("krb5_cc_get_principal, kerr 0x%x", kerr);
549 544 goto errout;
550 545 }
551 546 ss->ss_krb5clp = kprin;
552 547
553 548 /* Success! */
554 549 DPRINT("Ticket cache: %s:%s",
555 550 krb5_cc_get_type(kctx, kcc),
556 551 krb5_cc_get_name(kctx, kcc));
557 552 return (0);
558 553
559 554 errout:
560 555 krb5ssp_destroy(sp);
561 556 return (ENOTSUP);
562 557 }
|
↓ open down ↓ |
123 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX