Print this page
NEX-15724 SMB3 clients fail to connect using FQDN
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-15724 SMB3 clients fail to connect using FQDN
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-10231 SMB logon fails in fksmbd
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
NEX-5273 SMB 3 Encryption
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-5560 smb2 should use 64-bit server-global uids
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-3728 SMB1 signing should use KCF like SMB2/3
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Dan Fields <dan.fields@nexenta.com>
NEX-3610 CLONE NEX-3591 SMB3 signing
Reviewed by: Gordon Ross <gwr@nexenta.com>
Reviewed by: Dan Fields <dan.fields@nexenta.com>
NEX-3080 SMB1 signing problem with Kerberos auth.
Reviewed by: Bayard Bell <bayard.bell@nexenta.com>
Reviewed by: Dan Fields <dan.fields@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-2869 SMB2 signing fails for multi-user clients like Citrix RDS
SMB-55 SMB2 signing (syslog noise)
SMB-55 SMB2 signing
| Split |
Close |
| Expand all |
| Collapse all |
--- old/usr/src/uts/common/fs/smbsrv/smb2_signing.c
+++ new/usr/src/uts/common/fs/smbsrv/smb2_signing.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
|
↓ open down ↓ |
12 lines elided |
↑ open up ↑ |
13 13 * When distributing Covered Code, include this CDDL HEADER in each
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21 /*
22 22 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
23 - * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
23 + * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
24 24 */
25 25 /*
26 26 * These routines provide the SMB MAC signing for the SMB2 server.
27 27 * The routines calculate the signature of a SMB message in an mbuf chain.
28 28 *
29 29 * The following table describes the client server
30 30 * signing registry relationship
31 31 *
32 32 * | Required | Enabled | Disabled
33 33 * -------------+---------------+------------ +--------------
34 34 * Required | Signed | Signed | Fail
35 35 * -------------+---------------+-------------+-----------------
36 36 * Enabled | Signed | Signed | Not Signed
37 37 * -------------+---------------+-------------+----------------
38 38 * Disabled | Fail | Not Signed | Not Signed
39 39 */
40 40
41 41 #include <sys/uio.h>
42 -#include <smbsrv/smb_kproto.h>
43 -#include <smbsrv/smb_signing.h>
42 +#include <smbsrv/smb2_kproto.h>
43 +#include <smbsrv/smb_kcrypt.h>
44 44 #include <sys/isa_defs.h>
45 45 #include <sys/byteorder.h>
46 46 #include <sys/cmn_err.h>
47 47
48 48 #define SMB2_SIG_OFFS 48
49 49 #define SMB2_SIG_SIZE 16
50 50
51 +typedef struct mac_ops {
52 + int (*mac_init)(smb_sign_ctx_t *, smb_crypto_mech_t *,
53 + uint8_t *, size_t);
54 + int (*mac_update)(smb_sign_ctx_t, uint8_t *, size_t);
55 + int (*mac_final)(smb_sign_ctx_t, uint8_t *);
56 +} mac_ops_t;
57 +
58 +static int smb2_sign_calc_common(smb_request_t *, struct mbuf_chain *,
59 + uint8_t *, mac_ops_t *);
60 +
51 61 /*
62 + * SMB2 wrapper functions
63 + */
64 +
65 +static mac_ops_t
66 +smb2_sign_ops = {
67 + smb2_hmac_init,
68 + smb2_hmac_update,
69 + smb2_hmac_final
70 +};
71 +
72 +static int
73 +smb2_sign_calc(smb_request_t *sr,
74 + struct mbuf_chain *mbc,
75 + uint8_t *digest16)
76 +{
77 + int rv;
78 +
79 + rv = smb2_sign_calc_common(sr, mbc, digest16, &smb2_sign_ops);
80 +
81 + return (rv);
82 +}
83 +
84 +/*
52 85 * Called during session destroy.
53 86 */
54 87 static void
55 88 smb2_sign_fini(smb_session_t *s)
56 89 {
57 - smb_sign_mech_t *mech;
90 + smb_crypto_mech_t *mech;
58 91
59 92 if ((mech = s->sign_mech) != NULL) {
60 93 kmem_free(mech, sizeof (*mech));
61 94 s->sign_mech = NULL;
62 95 }
63 96 }
64 97
65 98 /*
99 + * SMB3 wrapper functions
100 + */
101 +
102 +static struct mac_ops
103 +smb3_sign_ops = {
104 + smb3_cmac_init,
105 + smb3_cmac_update,
106 + smb3_cmac_final
107 +};
108 +
109 +static int
110 +smb3_sign_calc(smb_request_t *sr,
111 + struct mbuf_chain *mbc,
112 + uint8_t *digest16)
113 +{
114 + int rv;
115 +
116 + rv = smb2_sign_calc_common(sr, mbc, digest16, &smb3_sign_ops);
117 +
118 + return (rv);
119 +}
120 +
121 +/*
122 + * Input to KDF for SigningKey.
123 + * See comment for smb3_do_kdf for content.
124 + */
125 +static uint8_t sign_kdf_input[29] = {
126 + 0, 0, 0, 1, 'S', 'M', 'B', '2',
127 + 'A', 'E', 'S', 'C', 'M', 'A', 'C', 0,
128 + 0, 'S', 'm', 'b', 'S', 'i', 'g', 'n',
129 + 0, 0, 0, 0, 0x80 };
130 +
131 +void
132 +smb2_sign_init_mech(smb_session_t *s)
133 +{
134 + smb_crypto_mech_t *mech;
135 + int (*get_mech)(smb_crypto_mech_t *);
136 + int (*sign_calc)(smb_request_t *, struct mbuf_chain *, uint8_t *);
137 + int rc;
138 +
139 + if (s->sign_mech != NULL)
140 + return;
141 +
142 + if (s->dialect >= SMB_VERS_3_0) {
143 + get_mech = smb3_cmac_getmech;
144 + sign_calc = smb3_sign_calc;
145 + } else {
146 + get_mech = smb2_hmac_getmech;
147 + sign_calc = smb2_sign_calc;
148 + }
149 +
150 + mech = kmem_zalloc(sizeof (*mech), KM_SLEEP);
151 + rc = get_mech(mech);
152 + if (rc != 0) {
153 + kmem_free(mech, sizeof (*mech));
154 + return;
155 + }
156 + s->sign_mech = mech;
157 + s->sign_calc = sign_calc;
158 + s->sign_fini = smb2_sign_fini;
159 +}
160 +
161 +/*
66 162 * smb2_sign_begin
163 + * Handles both SMB2 & SMB3
67 164 *
68 165 * Get the mechanism info.
69 166 * Intializes MAC key based on the user session key and store it in
70 167 * the signing structure. This begins signing on this session.
71 168 */
72 -int
169 +void
73 170 smb2_sign_begin(smb_request_t *sr, smb_token_t *token)
74 171 {
75 172 smb_session_t *s = sr->session;
76 173 smb_user_t *u = sr->uid_user;
77 174 struct smb_key *sign_key = &u->u_sign_key;
78 - smb_sign_mech_t *mech;
79 - int rc;
80 175
176 + sign_key->len = 0;
177 +
81 178 /*
82 179 * We should normally have a session key here because
83 180 * our caller filters out Anonymous and Guest logons.
84 181 * However, buggy clients could get us here without a
85 182 * session key, in which case we'll fail later when a
86 183 * request that requires signing can't be checked.
184 + * Also, don't bother initializing if we don't have a mechanism.
87 185 */
88 - if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0)
89 - return (0);
186 + if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0 ||
187 + s->sign_mech == NULL)
188 + return;
90 189
91 190 /*
92 - * Session-level initialization (once per session)
93 - * Get mech handle, sign_fini function.
94 - */
95 - smb_rwx_rwenter(&s->s_lock, RW_WRITER);
96 - if (s->sign_mech == NULL) {
97 - mech = kmem_zalloc(sizeof (*mech), KM_SLEEP);
98 - rc = smb2_hmac_getmech(mech);
99 - if (rc != 0) {
100 - kmem_free(mech, sizeof (*mech));
101 - smb_rwx_rwexit(&s->s_lock);
102 - return (rc);
103 - }
104 - s->sign_mech = mech;
105 - s->sign_fini = smb2_sign_fini;
106 - }
107 - smb_rwx_rwexit(&s->s_lock);
108 -
109 - /*
110 191 * Compute and store the signing key, which lives in
111 192 * the user structure.
112 193 */
113 - sign_key->len = SMB2_SIG_SIZE;
194 + if (s->dialect >= SMB_VERS_3_0) {
195 + /*
196 + * For SMB3, the signing key is a "KDF" hash of the
197 + * session key.
198 + */
199 + if (smb3_do_kdf(sign_key->key, sign_kdf_input,
200 + sizeof (sign_kdf_input), token->tkn_ssnkey.val,
201 + token->tkn_ssnkey.len) != 0)
202 + return;
203 + sign_key->len = SMB3_KEYLEN;
204 + } else {
205 + /*
206 + * For SMB2, the signing key is just the first 16 bytes
207 + * of the session key (truncated or padded with zeros).
208 + * [MS-SMB2] 3.2.5.3.1
209 + */
210 + sign_key->len = SMB2_KEYLEN;
211 + bcopy(token->tkn_ssnkey.val, sign_key->key,
212 + MIN(token->tkn_ssnkey.len, sign_key->len));
213 + }
114 214
115 - /*
116 - * For SMB2, the signing key is just the first 16 bytes
117 - * of the session key (truncated or padded with zeros).
118 - * [MS-SMB2] 3.2.5.3.1
119 - */
120 - bcopy(token->tkn_ssnkey.val, sign_key->key,
121 - MIN(token->tkn_ssnkey.len, sign_key->len));
122 -
123 215 mutex_enter(&u->u_mutex);
124 - if (s->secmode & SMB2_NEGOTIATE_SIGNING_ENABLED)
216 + if ((s->srv_secmode & SMB2_NEGOTIATE_SIGNING_ENABLED) != 0)
125 217 u->u_sign_flags |= SMB_SIGNING_ENABLED;
126 - if (s->secmode & SMB2_NEGOTIATE_SIGNING_REQUIRED)
218 + if ((s->srv_secmode & SMB2_NEGOTIATE_SIGNING_REQUIRED) != 0 ||
219 + (s->cli_secmode & SMB2_NEGOTIATE_SIGNING_REQUIRED) != 0)
127 220 u->u_sign_flags |=
128 221 SMB_SIGNING_ENABLED | SMB_SIGNING_CHECK;
129 222 mutex_exit(&u->u_mutex);
130 223
131 224 /*
132 225 * If we just turned on signing, the current request
133 226 * (an SMB2 session setup) will have come in without
134 227 * SMB2_FLAGS_SIGNED (and not signed) but the response
135 228 * is is supposed to be signed. [MS-SMB2] 3.3.5.5
136 229 */
137 230 if (u->u_sign_flags & SMB_SIGNING_ENABLED)
138 231 sr->smb2_hdr_flags |= SMB2_FLAGS_SIGNED;
139 -
140 - return (0);
141 232 }
142 233
143 234 /*
144 - * smb2_sign_calc
235 + * smb2_sign_calc_common
145 236 *
146 237 * Calculates MAC signature for the given buffer and returns
147 238 * it in the mac_sign parameter.
148 239 *
149 - * The signature is in the last 16 bytes of the SMB2 header.
150 - * The signature algorighm is to compute HMAC SHA256 over the
151 - * entire command, with the signature field set to zeros.
240 + * The signature algorithm is to compute HMAC SHA256 or AES_CMAC
241 + * over the entire command, with the signature field set to zeros.
152 242 *
153 243 * Return 0 if success else -1
154 244 */
245 +
155 246 static int
156 -smb2_sign_calc(smb_request_t *sr, struct mbuf_chain *mbc,
157 - uint8_t *digest)
247 +smb2_sign_calc_common(smb_request_t *sr, struct mbuf_chain *mbc,
248 + uint8_t *digest, mac_ops_t *ops)
158 249 {
159 250 uint8_t tmp_hdr[SMB2_HDR_SIZE];
160 251 smb_sign_ctx_t ctx = 0;
161 252 smb_session_t *s = sr->session;
162 253 smb_user_t *u = sr->uid_user;
163 254 struct smb_key *sign_key = &u->u_sign_key;
164 255 struct mbuf *mbuf;
165 256 int offset, resid, tlen, rc;
166 257
167 258 if (s->sign_mech == NULL || sign_key->len == 0)
168 259 return (-1);
169 260
170 - rc = smb2_hmac_init(&ctx, s->sign_mech, sign_key->key, sign_key->len);
261 + /* smb2_hmac_init or smb3_cmac_init */
262 + rc = ops->mac_init(&ctx, s->sign_mech, sign_key->key, sign_key->len);
171 263 if (rc != 0)
172 264 return (rc);
173 265
174 266 /*
175 267 * Work with a copy of the SMB2 header so we can
176 268 * clear the signature field without modifying
177 269 * the original message.
178 270 */
179 271 tlen = SMB2_HDR_SIZE;
180 272 offset = mbc->chain_offset;
181 273 resid = mbc->max_bytes - offset;
182 274 if (smb_mbc_peek(mbc, offset, "#c", tlen, tmp_hdr) != 0)
183 275 return (-1);
184 276 bzero(tmp_hdr + SMB2_SIG_OFFS, SMB2_SIG_SIZE);
185 - if ((rc = smb2_hmac_update(ctx, tmp_hdr, tlen)) != 0)
277 + /* smb2_hmac_update or smb3_cmac_update */
278 + if ((rc = ops->mac_update(ctx, tmp_hdr, tlen)) != 0)
186 279 return (rc);
187 280 offset += tlen;
188 281 resid -= tlen;
189 282
190 283 /*
191 284 * Digest the rest of the SMB packet, starting at the data
192 285 * just after the SMB header.
193 286 *
194 287 * Advance to the src mbuf where we start digesting.
195 288 */
196 289 mbuf = mbc->chain;
197 290 while (mbuf != NULL && (offset >= mbuf->m_len)) {
198 291 offset -= mbuf->m_len;
199 292 mbuf = mbuf->m_next;
200 293 }
201 294
202 295 if (mbuf == NULL)
|
↓ open down ↓ |
7 lines elided |
↑ open up ↑ |
203 296 return (-1);
204 297
205 298 /*
206 299 * Digest the remainder of this mbuf, limited to the
207 300 * residual count, and starting at the current offset.
208 301 * (typically SMB2_HDR_SIZE)
209 302 */
210 303 tlen = mbuf->m_len - offset;
211 304 if (tlen > resid)
212 305 tlen = resid;
213 - rc = smb2_hmac_update(ctx, (uint8_t *)mbuf->m_data + offset, tlen);
306 + /* smb2_hmac_update or smb3_cmac_update */
307 + rc = ops->mac_update(ctx, (uint8_t *)mbuf->m_data + offset, tlen);
214 308 if (rc != 0)
215 309 return (rc);
216 310 resid -= tlen;
217 311
218 312 /*
219 313 * Digest any more mbufs in the chain.
220 314 */
221 315 while (resid > 0) {
222 316 mbuf = mbuf->m_next;
223 317 if (mbuf == NULL)
224 318 return (-1);
225 319 tlen = mbuf->m_len;
226 320 if (tlen > resid)
227 321 tlen = resid;
228 - rc = smb2_hmac_update(ctx, (uint8_t *)mbuf->m_data, tlen);
322 + rc = ops->mac_update(ctx, (uint8_t *)mbuf->m_data, tlen);
229 323 if (rc != 0)
230 324 return (rc);
231 325 resid -= tlen;
232 326 }
233 327
234 328 /*
329 + * smb2_hmac_final or smb3_cmac_final
235 330 * Note: digest is _always_ SMB2_SIG_SIZE,
236 331 * even if the mech uses a longer one.
332 + *
333 + * smb2_hmac_update or smb3_cmac_update
237 334 */
238 - if ((rc = smb2_hmac_final(ctx, digest)) != 0)
335 + if ((rc = ops->mac_final(ctx, digest)) != 0)
239 336 return (rc);
240 337
241 338 return (0);
242 339 }
243 340
244 341 /*
245 342 * smb2_sign_check_request
246 343 *
247 344 * Calculates MAC signature for the request mbuf chain
248 345 * using the next expected sequence number and compares
249 346 * it to the given signature.
250 347 *
251 348 * Note it does not check the signature for secondary transactions
252 349 * as their sequence number is the same as the original request.
|
↓ open down ↓ |
4 lines elided |
↑ open up ↑ |
253 350 *
254 351 * Return 0 if the signature verifies, otherwise, returns -1;
255 352 *
256 353 */
257 354 int
258 355 smb2_sign_check_request(smb_request_t *sr)
259 356 {
260 357 uint8_t req_sig[SMB2_SIG_SIZE];
261 358 uint8_t vfy_sig[SMB2_SIG_SIZE];
262 359 struct mbuf_chain *mbc = &sr->smb_data;
360 + smb_session_t *s = sr->session;
263 361 smb_user_t *u = sr->uid_user;
264 362 int sig_off;
265 363
266 364 /*
267 365 * Don't check commands with a zero session ID.
268 366 * [MS-SMB2] 3.3.4.1.1
269 367 */
270 - if (sr->smb_uid == 0 || u == NULL)
368 + if (sr->smb2_ssnid == 0 || u == NULL)
271 369 return (0);
272 370
371 + /* In case _sign_begin failed. */
372 + if (s->sign_calc == NULL)
373 + return (-1);
374 +
273 375 /* Get the request signature. */
274 376 sig_off = sr->smb2_cmd_hdr + SMB2_SIG_OFFS;
275 377 if (smb_mbc_peek(mbc, sig_off, "#c", SMB2_SIG_SIZE, req_sig) != 0)
276 378 return (-1);
277 379
278 380 /*
279 381 * Compute the correct signature and compare.
382 + * smb2_sign_calc() or smb3_sign_calc()
280 383 */
281 - if (smb2_sign_calc(sr, mbc, vfy_sig) != 0)
384 + if (s->sign_calc(sr, mbc, vfy_sig) != 0)
282 385 return (-1);
283 386 if (memcmp(vfy_sig, req_sig, SMB2_SIG_SIZE) != 0) {
284 387 cmn_err(CE_NOTE, "smb2_sign_check_request: bad signature");
285 388 return (-1);
286 389 }
287 390
288 391 return (0);
289 392 }
290 393
291 394 /*
292 395 * smb2_sign_reply
|
↓ open down ↓ |
1 lines elided |
↑ open up ↑ |
293 396 *
294 397 * Calculates MAC signature for the given mbuf chain,
295 398 * and write it to the signature field in the mbuf.
296 399 *
297 400 */
298 401 void
299 402 smb2_sign_reply(smb_request_t *sr)
300 403 {
301 404 uint8_t reply_sig[SMB2_SIG_SIZE];
302 405 struct mbuf_chain tmp_mbc;
406 + smb_session_t *s = sr->session;
303 407 smb_user_t *u = sr->uid_user;
304 408 int hdr_off, msg_len;
305 409
306 410 if (u == NULL)
307 411 return;
412 + if (s->sign_calc == NULL)
413 + return;
308 414
309 415 msg_len = sr->reply.chain_offset - sr->smb2_reply_hdr;
310 416 (void) MBC_SHADOW_CHAIN(&tmp_mbc, &sr->reply,
311 417 sr->smb2_reply_hdr, msg_len);
312 418
313 419 /*
314 420 * Calculate the MAC signature for this reply.
421 + * smb2_sign_calc() or smb3_sign_calc()
315 422 */
316 - if (smb2_sign_calc(sr, &tmp_mbc, reply_sig) != 0)
423 + if (s->sign_calc(sr, &tmp_mbc, reply_sig) != 0)
317 424 return;
318 425
319 426 /*
320 427 * Poke the signature into the response.
321 428 */
322 429 hdr_off = sr->smb2_reply_hdr + SMB2_SIG_OFFS;
323 430 (void) smb_mbc_poke(&sr->reply, hdr_off, "#c",
324 431 SMB2_SIG_SIZE, reply_sig);
432 +}
433 +
434 +/*
435 + * Derive SMB3 key as described in [MS-SMB2] 3.1.4.2
436 + * and [NIST SP800-108]
437 + *
438 + * r = 32, L = 128, PRF = HMAC-SHA256, key = (session key)
439 + *
440 + * Note that these describe pre-3.1.1 inputs.
441 + *
442 + * Session.SigningKey for binding a session:
443 + * - Session.SessionKey as K1
444 + * - label = SMB2AESCMAC (size 12)
445 + * - context = SmbSign (size 8)
446 + * Channel.SigningKey for for all other requests
447 + * - if SMB2_SESSION_FLAG_BINDING, GSS key (in Session.SessionKey?) as K1;
448 + * - otherwise, Session.SessionKey as K1
449 + * - label = SMB2AESCMAC (size 12)
450 + * - context = SmbSign (size 8)
451 + * Session.ApplicationKey for ... (not sure what yet)
452 + * - Session.SessionKey as K1
453 + * - label = SMB2APP (size 8)
454 + * - context = SmbRpc (size 7)
455 + * Session.EncryptionKey for encrypting server messages
456 + * - Session.SessionKey as K1
457 + * - label = "SMB2AESCCM" (size 11)
458 + * - context = "ServerOut" (size 10)
459 + * Session.DecryptionKey for decrypting client requests
460 + * - Session.SessionKey as K1
461 + * - label = "SMB2AESCCM" (size 11)
462 + * - context = "ServerIn " (size 10) (Note the space)
463 + */
464 +
465 +int
466 +smb3_do_kdf(void *outbuf, void *input, size_t input_len,
467 + uint8_t *key, uint32_t key_len)
468 +{
469 + uint8_t digest32[SHA256_DIGEST_LENGTH];
470 + smb_crypto_mech_t mech;
471 + smb_sign_ctx_t hctx = 0;
472 + int rc;
473 +
474 + bzero(&mech, sizeof (mech));
475 + if ((rc = smb2_hmac_getmech(&mech)) != 0)
476 + return (rc);
477 +
478 + /* Limit the SessionKey input to its maximum size (16 bytes) */
479 + rc = smb2_hmac_init(&hctx, &mech, key, MIN(key_len, SMB2_KEYLEN));
480 + if (rc != 0)
481 + return (rc);
482 +
483 + if ((rc = smb2_hmac_update(hctx, input, input_len)) != 0)
484 + return (rc);
485 +
486 + if ((rc = smb2_hmac_final(hctx, digest32)) != 0)
487 + return (rc);
488 +
489 + /* Output is first 16 bytes of digest. */
490 + bcopy(digest32, outbuf, SMB3_KEYLEN);
491 + return (0);
325 492 }
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX