]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * fs/cifs/cifsencrypt.c | |
3 | * | |
8b7a4544 SF |
4 | * Encryption and hashing operations relating to NTLM, NTLMv2. See MS-NLMP |
5 | * for more detailed information | |
6 | * | |
95dc8dd1 | 7 | * Copyright (C) International Business Machines Corp., 2005,2013 |
1da177e4 LT |
8 | * Author(s): Steve French (sfrench@us.ibm.com) |
9 | * | |
10 | * This library is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU Lesser General Public License as published | |
12 | * by the Free Software Foundation; either version 2.1 of the License, or | |
13 | * (at your option) any later version. | |
14 | * | |
15 | * This library is distributed in the hope that it will be useful, | |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See | |
18 | * the GNU Lesser General Public License for more details. | |
19 | * | |
20 | * You should have received a copy of the GNU Lesser General Public License | |
21 | * along with this library; if not, write to the Free Software | |
22 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
23 | */ | |
24 | ||
25 | #include <linux/fs.h> | |
5a0e3ad6 | 26 | #include <linux/slab.h> |
1da177e4 | 27 | #include "cifspdu.h" |
ffdd6e4d | 28 | #include "cifsglob.h" |
1da177e4 | 29 | #include "cifs_debug.h" |
1da177e4 LT |
30 | #include "cifs_unicode.h" |
31 | #include "cifsproto.h" | |
2b149f11 | 32 | #include "ntlmssp.h" |
7c7b25bc | 33 | #include <linux/ctype.h> |
6d027cfd | 34 | #include <linux/random.h> |
fb308a6f | 35 | #include <linux/highmem.h> |
9651ddba | 36 | #include <crypto/skcipher.h> |
1da177e4 | 37 | |
95dc8dd1 SF |
38 | static int |
39 | cifs_crypto_shash_md5_allocate(struct TCP_Server_Info *server) | |
40 | { | |
41 | int rc; | |
42 | unsigned int size; | |
43 | ||
44 | if (server->secmech.sdescmd5 != NULL) | |
45 | return 0; /* already allocated */ | |
46 | ||
47 | server->secmech.md5 = crypto_alloc_shash("md5", 0, 0); | |
48 | if (IS_ERR(server->secmech.md5)) { | |
49 | cifs_dbg(VFS, "could not allocate crypto md5\n"); | |
ba482029 JL |
50 | rc = PTR_ERR(server->secmech.md5); |
51 | server->secmech.md5 = NULL; | |
52 | return rc; | |
95dc8dd1 SF |
53 | } |
54 | ||
55 | size = sizeof(struct shash_desc) + | |
56 | crypto_shash_descsize(server->secmech.md5); | |
57 | server->secmech.sdescmd5 = kmalloc(size, GFP_KERNEL); | |
58 | if (!server->secmech.sdescmd5) { | |
95dc8dd1 SF |
59 | crypto_free_shash(server->secmech.md5); |
60 | server->secmech.md5 = NULL; | |
ba482029 | 61 | return -ENOMEM; |
95dc8dd1 SF |
62 | } |
63 | server->secmech.sdescmd5->shash.tfm = server->secmech.md5; | |
64 | server->secmech.sdescmd5->shash.flags = 0x0; | |
65 | ||
66 | return 0; | |
67 | } | |
68 | ||
16c568ef AV |
69 | int __cifs_calc_signature(struct smb_rqst *rqst, |
70 | struct TCP_Server_Info *server, char *signature, | |
71 | struct shash_desc *shash) | |
84afc29b | 72 | { |
e9917a00 | 73 | int i; |
307fbd31 | 74 | int rc; |
bf5ea0e2 JL |
75 | struct kvec *iov = rqst->rq_iov; |
76 | int n_vec = rqst->rq_nvec; | |
84afc29b | 77 | |
50c2f753 | 78 | for (i = 0; i < n_vec; i++) { |
745542e2 JL |
79 | if (iov[i].iov_len == 0) |
80 | continue; | |
ffdd6e4d | 81 | if (iov[i].iov_base == NULL) { |
f96637be | 82 | cifs_dbg(VFS, "null iovec entry\n"); |
e9917a00 | 83 | return -EIO; |
745542e2 | 84 | } |
ffdd6e4d | 85 | /* The first entry includes a length field (which does not get |
e9917a00 | 86 | signed that occupies the first 4 bytes before the header */ |
ffdd6e4d | 87 | if (i == 0) { |
63d2583f | 88 | if (iov[0].iov_len <= 8) /* cmd field at offset 9 */ |
e9917a00 | 89 | break; /* nothing to sign or corrupt header */ |
16c568ef | 90 | rc = crypto_shash_update(shash, |
307fbd31 | 91 | iov[i].iov_base + 4, iov[i].iov_len - 4); |
14cae324 | 92 | } else { |
16c568ef | 93 | rc = crypto_shash_update(shash, |
307fbd31 | 94 | iov[i].iov_base, iov[i].iov_len); |
14cae324 SP |
95 | } |
96 | if (rc) { | |
f96637be JP |
97 | cifs_dbg(VFS, "%s: Could not update with payload\n", |
98 | __func__); | |
14cae324 SP |
99 | return rc; |
100 | } | |
e9917a00 | 101 | } |
84afc29b | 102 | |
fb308a6f JL |
103 | /* now hash over the rq_pages array */ |
104 | for (i = 0; i < rqst->rq_npages; i++) { | |
16c568ef AV |
105 | void *kaddr = kmap(rqst->rq_pages[i]); |
106 | size_t len = rqst->rq_pagesz; | |
107 | ||
108 | if (i == rqst->rq_npages - 1) | |
109 | len = rqst->rq_tailsz; | |
110 | ||
111 | crypto_shash_update(shash, kaddr, len); | |
fb308a6f | 112 | |
fb308a6f JL |
113 | kunmap(rqst->rq_pages[i]); |
114 | } | |
115 | ||
16c568ef | 116 | rc = crypto_shash_final(shash, signature); |
14cae324 | 117 | if (rc) |
16c568ef | 118 | cifs_dbg(VFS, "%s: Could not generate hash\n", __func__); |
84afc29b | 119 | |
307fbd31 | 120 | return rc; |
84afc29b SF |
121 | } |
122 | ||
16c568ef AV |
123 | /* |
124 | * Calculate and return the CIFS signature based on the mac key and SMB PDU. | |
125 | * The 16 byte signature must be allocated by the caller. Note we only use the | |
126 | * 1st eight bytes and that the smb header signature field on input contains | |
127 | * the sequence number before this function is called. Also, this function | |
128 | * should be called with the server->srv_mutex held. | |
129 | */ | |
130 | static int cifs_calc_signature(struct smb_rqst *rqst, | |
131 | struct TCP_Server_Info *server, char *signature) | |
132 | { | |
133 | int rc; | |
134 | ||
135 | if (!rqst->rq_iov || !signature || !server) | |
136 | return -EINVAL; | |
137 | ||
138 | if (!server->secmech.sdescmd5) { | |
139 | rc = cifs_crypto_shash_md5_allocate(server); | |
140 | if (rc) { | |
141 | cifs_dbg(VFS, "%s: Can't alloc md5 crypto\n", __func__); | |
142 | return -1; | |
143 | } | |
144 | } | |
145 | ||
146 | rc = crypto_shash_init(&server->secmech.sdescmd5->shash); | |
147 | if (rc) { | |
148 | cifs_dbg(VFS, "%s: Could not init md5\n", __func__); | |
149 | return rc; | |
150 | } | |
151 | ||
152 | rc = crypto_shash_update(&server->secmech.sdescmd5->shash, | |
153 | server->session_key.response, server->session_key.len); | |
154 | if (rc) { | |
155 | cifs_dbg(VFS, "%s: Could not update with response\n", __func__); | |
156 | return rc; | |
157 | } | |
158 | ||
159 | return __cifs_calc_signature(rqst, server, signature, | |
160 | &server->secmech.sdescmd5->shash); | |
161 | } | |
162 | ||
a0f8b4fb | 163 | /* must be called with server->srv_mutex held */ |
bf5ea0e2 | 164 | int cifs_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server, |
63d2583f | 165 | __u32 *pexpected_response_sequence_number) |
84afc29b SF |
166 | { |
167 | int rc = 0; | |
168 | char smb_signature[20]; | |
bf5ea0e2 | 169 | struct smb_hdr *cifs_pdu = (struct smb_hdr *)rqst->rq_iov[0].iov_base; |
84afc29b | 170 | |
ffdd6e4d | 171 | if ((cifs_pdu == NULL) || (server == NULL)) |
84afc29b SF |
172 | return -EINVAL; |
173 | ||
998d6fcb JL |
174 | if (!(cifs_pdu->Flags2 & SMBFLG2_SECURITY_SIGNATURE) || |
175 | server->tcpStatus == CifsNeedNegotiate) | |
84afc29b SF |
176 | return rc; |
177 | ||
998d6fcb | 178 | if (!server->session_estab) { |
b4dacbc2 | 179 | memcpy(cifs_pdu->Signature.SecuritySignature, "BSRSPYL", 8); |
998d6fcb JL |
180 | return rc; |
181 | } | |
182 | ||
ffdd6e4d | 183 | cifs_pdu->Signature.Sequence.SequenceNumber = |
84afc29b | 184 | cpu_to_le32(server->sequence_number); |
ffdd6e4d | 185 | cifs_pdu->Signature.Sequence.Reserved = 0; |
84afc29b | 186 | |
0124cc45 JL |
187 | *pexpected_response_sequence_number = ++server->sequence_number; |
188 | ++server->sequence_number; | |
84afc29b | 189 | |
bf5ea0e2 | 190 | rc = cifs_calc_signature(rqst, server, smb_signature); |
ffdd6e4d SF |
191 | if (rc) |
192 | memset(cifs_pdu->Signature.SecuritySignature, 0, 8); | |
193 | else | |
194 | memcpy(cifs_pdu->Signature.SecuritySignature, smb_signature, 8); | |
84afc29b | 195 | |
ffdd6e4d | 196 | return rc; |
84afc29b SF |
197 | } |
198 | ||
bf5ea0e2 JL |
199 | int cifs_sign_smbv(struct kvec *iov, int n_vec, struct TCP_Server_Info *server, |
200 | __u32 *pexpected_response_sequence) | |
201 | { | |
202 | struct smb_rqst rqst = { .rq_iov = iov, | |
203 | .rq_nvec = n_vec }; | |
204 | ||
205 | return cifs_sign_rqst(&rqst, server, pexpected_response_sequence); | |
206 | } | |
207 | ||
826a95e4 JL |
208 | /* must be called with server->srv_mutex held */ |
209 | int cifs_sign_smb(struct smb_hdr *cifs_pdu, struct TCP_Server_Info *server, | |
210 | __u32 *pexpected_response_sequence_number) | |
211 | { | |
212 | struct kvec iov; | |
213 | ||
214 | iov.iov_base = cifs_pdu; | |
215 | iov.iov_len = be32_to_cpu(cifs_pdu->smb_buf_length) + 4; | |
216 | ||
762a4206 | 217 | return cifs_sign_smbv(&iov, 1, server, |
826a95e4 JL |
218 | pexpected_response_sequence_number); |
219 | } | |
220 | ||
bf5ea0e2 | 221 | int cifs_verify_signature(struct smb_rqst *rqst, |
21e73393 | 222 | struct TCP_Server_Info *server, |
ffdd6e4d | 223 | __u32 expected_sequence_number) |
1da177e4 | 224 | { |
c8e56f1f | 225 | unsigned int rc; |
1da177e4 LT |
226 | char server_response_sig[8]; |
227 | char what_we_think_sig_should_be[20]; | |
bf5ea0e2 | 228 | struct smb_hdr *cifs_pdu = (struct smb_hdr *)rqst->rq_iov[0].iov_base; |
1da177e4 | 229 | |
21e73393 | 230 | if (cifs_pdu == NULL || server == NULL) |
1da177e4 LT |
231 | return -EINVAL; |
232 | ||
9c4843ea | 233 | if (!server->session_estab) |
1da177e4 LT |
234 | return 0; |
235 | ||
236 | if (cifs_pdu->Command == SMB_COM_LOCKING_ANDX) { | |
50c2f753 | 237 | struct smb_com_lock_req *pSMB = |
ffdd6e4d SF |
238 | (struct smb_com_lock_req *)cifs_pdu; |
239 | if (pSMB->LockType & LOCKING_ANDX_OPLOCK_RELEASE) | |
1da177e4 LT |
240 | return 0; |
241 | } | |
242 | ||
50c2f753 SF |
243 | /* BB what if signatures are supposed to be on for session but |
244 | server does not send one? BB */ | |
245 | ||
1da177e4 | 246 | /* Do not need to verify session setups with signature "BSRSPYL " */ |
50c2f753 | 247 | if (memcmp(cifs_pdu->Signature.SecuritySignature, "BSRSPYL ", 8) == 0) |
f96637be JP |
248 | cifs_dbg(FYI, "dummy signature received for smb command 0x%x\n", |
249 | cifs_pdu->Command); | |
1da177e4 LT |
250 | |
251 | /* save off the origiginal signature so we can modify the smb and check | |
252 | its signature against what the server sent */ | |
50c2f753 | 253 | memcpy(server_response_sig, cifs_pdu->Signature.SecuritySignature, 8); |
1da177e4 | 254 | |
50c2f753 SF |
255 | cifs_pdu->Signature.Sequence.SequenceNumber = |
256 | cpu_to_le32(expected_sequence_number); | |
1da177e4 LT |
257 | cifs_pdu->Signature.Sequence.Reserved = 0; |
258 | ||
157c2491 | 259 | mutex_lock(&server->srv_mutex); |
bf5ea0e2 | 260 | rc = cifs_calc_signature(rqst, server, what_we_think_sig_should_be); |
157c2491 | 261 | mutex_unlock(&server->srv_mutex); |
1da177e4 | 262 | |
50c2f753 | 263 | if (rc) |
1da177e4 LT |
264 | return rc; |
265 | ||
50c2f753 SF |
266 | /* cifs_dump_mem("what we think it should be: ", |
267 | what_we_think_sig_should_be, 16); */ | |
1da177e4 | 268 | |
50c2f753 | 269 | if (memcmp(server_response_sig, what_we_think_sig_should_be, 8)) |
1da177e4 LT |
270 | return -EACCES; |
271 | else | |
272 | return 0; | |
273 | ||
274 | } | |
275 | ||
21e73393 | 276 | /* first calculate 24 bytes ntlm response and then 16 byte session key */ |
9ef5992e | 277 | int setup_ntlm_response(struct cifs_ses *ses, const struct nls_table *nls_cp) |
1da177e4 | 278 | { |
ee2c9258 | 279 | int rc = 0; |
21e73393 SP |
280 | unsigned int temp_len = CIFS_SESS_KEY_SIZE + CIFS_AUTH_RESP_SIZE; |
281 | char temp_key[CIFS_SESS_KEY_SIZE]; | |
282 | ||
283 | if (!ses) | |
1da177e4 LT |
284 | return -EINVAL; |
285 | ||
21e73393 | 286 | ses->auth_key.response = kmalloc(temp_len, GFP_KERNEL); |
f96637be | 287 | if (!ses->auth_key.response) |
21e73393 | 288 | return -ENOMEM; |
f96637be | 289 | |
21e73393 SP |
290 | ses->auth_key.len = temp_len; |
291 | ||
ee2c9258 | 292 | rc = SMBNTencrypt(ses->password, ses->server->cryptkey, |
9ef5992e | 293 | ses->auth_key.response + CIFS_SESS_KEY_SIZE, nls_cp); |
ee2c9258 | 294 | if (rc) { |
f96637be JP |
295 | cifs_dbg(FYI, "%s Can't generate NTLM response, error: %d\n", |
296 | __func__, rc); | |
ee2c9258 SP |
297 | return rc; |
298 | } | |
299 | ||
9ef5992e | 300 | rc = E_md4hash(ses->password, temp_key, nls_cp); |
ee2c9258 | 301 | if (rc) { |
f96637be JP |
302 | cifs_dbg(FYI, "%s Can't generate NT hash, error: %d\n", |
303 | __func__, rc); | |
ee2c9258 SP |
304 | return rc; |
305 | } | |
21e73393 | 306 | |
ee2c9258 SP |
307 | rc = mdfour(ses->auth_key.response, temp_key, CIFS_SESS_KEY_SIZE); |
308 | if (rc) | |
f96637be JP |
309 | cifs_dbg(FYI, "%s Can't generate NTLM session key, error: %d\n", |
310 | __func__, rc); | |
21e73393 | 311 | |
ee2c9258 | 312 | return rc; |
1da177e4 LT |
313 | } |
314 | ||
7c7b25bc | 315 | #ifdef CONFIG_CIFS_WEAK_PW_HASH |
43988d76 | 316 | int calc_lanman_hash(const char *password, const char *cryptkey, bool encrypt, |
4e53a3fb | 317 | char *lnm_session_key) |
7c7b25bc SF |
318 | { |
319 | int i; | |
43988d76 | 320 | int rc; |
7c7b25bc SF |
321 | char password_with_pad[CIFS_ENCPWD_SIZE]; |
322 | ||
323 | memset(password_with_pad, 0, CIFS_ENCPWD_SIZE); | |
4e53a3fb JL |
324 | if (password) |
325 | strncpy(password_with_pad, password, CIFS_ENCPWD_SIZE); | |
326 | ||
04912d6a | 327 | if (!encrypt && global_secflags & CIFSSEC_MAY_PLNTXT) { |
4e53a3fb JL |
328 | memcpy(lnm_session_key, password_with_pad, |
329 | CIFS_ENCPWD_SIZE); | |
43988d76 | 330 | return 0; |
4e53a3fb | 331 | } |
bdc4bf6e | 332 | |
7c7b25bc SF |
333 | /* calculate old style session key */ |
334 | /* calling toupper is less broken than repeatedly | |
335 | calling nls_toupper would be since that will never | |
336 | work for UTF8, but neither handles multibyte code pages | |
337 | but the only alternative would be converting to UCS-16 (Unicode) | |
338 | (using a routine something like UniStrupr) then | |
339 | uppercasing and then converting back from Unicode - which | |
340 | would only worth doing it if we knew it were utf8. Basically | |
341 | utf8 and other multibyte codepages each need their own strupper | |
342 | function since a byte at a time will ont work. */ | |
343 | ||
ef571cad | 344 | for (i = 0; i < CIFS_ENCPWD_SIZE; i++) |
7c7b25bc | 345 | password_with_pad[i] = toupper(password_with_pad[i]); |
7c7b25bc | 346 | |
43988d76 | 347 | rc = SMBencrypt(password_with_pad, cryptkey, lnm_session_key); |
4e53a3fb | 348 | |
43988d76 | 349 | return rc; |
7c7b25bc SF |
350 | } |
351 | #endif /* CIFS_WEAK_PW_HASH */ | |
352 | ||
9daa42e2 SP |
353 | /* Build a proper attribute value/target info pairs blob. |
354 | * Fill in netbios and dns domain name and workstation name | |
355 | * and client time (total five av pairs and + one end of fields indicator. | |
356 | * Allocate domain name which gets freed when session struct is deallocated. | |
2b149f11 SP |
357 | */ |
358 | static int | |
96daf2b0 | 359 | build_avpair_blob(struct cifs_ses *ses, const struct nls_table *nls_cp) |
2b149f11 | 360 | { |
9daa42e2 | 361 | unsigned int dlen; |
cfbd6f84 | 362 | unsigned int size = 2 * sizeof(struct ntlmssp2_name); |
9daa42e2 SP |
363 | char *defdmname = "WORKGROUP"; |
364 | unsigned char *blobptr; | |
2b149f11 SP |
365 | struct ntlmssp2_name *attrptr; |
366 | ||
9daa42e2 SP |
367 | if (!ses->domainName) { |
368 | ses->domainName = kstrdup(defdmname, GFP_KERNEL); | |
369 | if (!ses->domainName) | |
370 | return -ENOMEM; | |
371 | } | |
372 | ||
373 | dlen = strlen(ses->domainName); | |
9daa42e2 | 374 | |
cfbd6f84 SP |
375 | /* |
376 | * The length of this blob is two times the size of a | |
377 | * structure (av pair) which holds name/size | |
378 | * ( for NTLMSSP_AV_NB_DOMAIN_NAME followed by NTLMSSP_AV_EOL ) + | |
379 | * unicode length of a netbios domain name | |
9daa42e2 | 380 | */ |
cfbd6f84 | 381 | ses->auth_key.len = size + 2 * dlen; |
d3686d54 SP |
382 | ses->auth_key.response = kzalloc(ses->auth_key.len, GFP_KERNEL); |
383 | if (!ses->auth_key.response) { | |
384 | ses->auth_key.len = 0; | |
2b149f11 SP |
385 | return -ENOMEM; |
386 | } | |
9daa42e2 | 387 | |
d3686d54 | 388 | blobptr = ses->auth_key.response; |
9daa42e2 SP |
389 | attrptr = (struct ntlmssp2_name *) blobptr; |
390 | ||
cfbd6f84 SP |
391 | /* |
392 | * As defined in MS-NTLM 3.3.2, just this av pair field | |
393 | * is sufficient as part of the temp | |
394 | */ | |
9daa42e2 SP |
395 | attrptr->type = cpu_to_le16(NTLMSSP_AV_NB_DOMAIN_NAME); |
396 | attrptr->length = cpu_to_le16(2 * dlen); | |
397 | blobptr = (unsigned char *)attrptr + sizeof(struct ntlmssp2_name); | |
acbbb76a | 398 | cifs_strtoUTF16((__le16 *)blobptr, ses->domainName, dlen, nls_cp); |
9daa42e2 | 399 | |
2b149f11 SP |
400 | return 0; |
401 | } | |
402 | ||
403 | /* Server has provided av pairs/target info in the type 2 challenge | |
404 | * packet and we have plucked it and stored within smb session. | |
405 | * We parse that blob here to find netbios domain name to be used | |
406 | * as part of ntlmv2 authentication (in Target String), if not already | |
407 | * specified on the command line. | |
408 | * If this function returns without any error but without fetching | |
409 | * domain name, authentication may fail against some server but | |
410 | * may not fail against other (those who are not very particular | |
411 | * about target string i.e. for some, just user name might suffice. | |
412 | */ | |
413 | static int | |
96daf2b0 | 414 | find_domain_name(struct cifs_ses *ses, const struct nls_table *nls_cp) |
2b149f11 SP |
415 | { |
416 | unsigned int attrsize; | |
417 | unsigned int type; | |
418 | unsigned int onesize = sizeof(struct ntlmssp2_name); | |
419 | unsigned char *blobptr; | |
420 | unsigned char *blobend; | |
421 | struct ntlmssp2_name *attrptr; | |
422 | ||
d3686d54 | 423 | if (!ses->auth_key.len || !ses->auth_key.response) |
2b149f11 SP |
424 | return 0; |
425 | ||
d3686d54 SP |
426 | blobptr = ses->auth_key.response; |
427 | blobend = blobptr + ses->auth_key.len; | |
2b149f11 SP |
428 | |
429 | while (blobptr + onesize < blobend) { | |
430 | attrptr = (struct ntlmssp2_name *) blobptr; | |
431 | type = le16_to_cpu(attrptr->type); | |
432 | if (type == NTLMSSP_AV_EOL) | |
433 | break; | |
434 | blobptr += 2; /* advance attr type */ | |
435 | attrsize = le16_to_cpu(attrptr->length); | |
436 | blobptr += 2; /* advance attr size */ | |
437 | if (blobptr + attrsize > blobend) | |
438 | break; | |
439 | if (type == NTLMSSP_AV_NB_DOMAIN_NAME) { | |
057d6332 | 440 | if (!attrsize || attrsize >= CIFS_MAX_DOMAINNAME_LEN) |
2b149f11 SP |
441 | break; |
442 | if (!ses->domainName) { | |
443 | ses->domainName = | |
444 | kmalloc(attrsize + 1, GFP_KERNEL); | |
445 | if (!ses->domainName) | |
446 | return -ENOMEM; | |
acbbb76a | 447 | cifs_from_utf16(ses->domainName, |
2b149f11 | 448 | (__le16 *)blobptr, attrsize, attrsize, |
b693855f | 449 | nls_cp, NO_MAP_UNI_RSVD); |
2b149f11 SP |
450 | break; |
451 | } | |
452 | } | |
453 | blobptr += attrsize; /* advance attr value */ | |
454 | } | |
455 | ||
456 | return 0; | |
457 | } | |
458 | ||
98ce94c8 PS |
459 | /* Server has provided av pairs/target info in the type 2 challenge |
460 | * packet and we have plucked it and stored within smb session. | |
461 | * We parse that blob here to find the server given timestamp | |
462 | * as part of ntlmv2 authentication (or local current time as | |
463 | * default in case of failure) | |
464 | */ | |
465 | static __le64 | |
466 | find_timestamp(struct cifs_ses *ses) | |
467 | { | |
468 | unsigned int attrsize; | |
469 | unsigned int type; | |
470 | unsigned int onesize = sizeof(struct ntlmssp2_name); | |
471 | unsigned char *blobptr; | |
472 | unsigned char *blobend; | |
473 | struct ntlmssp2_name *attrptr; | |
474 | ||
475 | if (!ses->auth_key.len || !ses->auth_key.response) | |
476 | return 0; | |
477 | ||
478 | blobptr = ses->auth_key.response; | |
479 | blobend = blobptr + ses->auth_key.len; | |
480 | ||
481 | while (blobptr + onesize < blobend) { | |
482 | attrptr = (struct ntlmssp2_name *) blobptr; | |
483 | type = le16_to_cpu(attrptr->type); | |
484 | if (type == NTLMSSP_AV_EOL) | |
485 | break; | |
486 | blobptr += 2; /* advance attr type */ | |
487 | attrsize = le16_to_cpu(attrptr->length); | |
488 | blobptr += 2; /* advance attr size */ | |
489 | if (blobptr + attrsize > blobend) | |
490 | break; | |
491 | if (type == NTLMSSP_AV_TIMESTAMP) { | |
492 | if (attrsize == sizeof(u64)) | |
493 | return *((__le64 *)blobptr); | |
494 | } | |
495 | blobptr += attrsize; /* advance attr value */ | |
496 | } | |
497 | ||
498 | return cpu_to_le64(cifs_UnixTimeToNT(CURRENT_TIME)); | |
499 | } | |
500 | ||
96daf2b0 | 501 | static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, |
50c2f753 | 502 | const struct nls_table *nls_cp) |
a8ee0344 SF |
503 | { |
504 | int rc = 0; | |
505 | int len; | |
307fbd31 | 506 | char nt_hash[CIFS_NTHASH_SIZE]; |
fdf96a90 | 507 | __le16 *user; |
50c2f753 | 508 | wchar_t *domain; |
307fbd31 | 509 | wchar_t *server; |
a8ee0344 | 510 | |
307fbd31 | 511 | if (!ses->server->secmech.sdeschmacmd5) { |
f96637be | 512 | cifs_dbg(VFS, "%s: can't generate ntlmv2 hash\n", __func__); |
307fbd31 SP |
513 | return -1; |
514 | } | |
56234e27 | 515 | |
c8e56f1f | 516 | /* calculate md4 hash of password */ |
9ef5992e | 517 | E_md4hash(ses->password, nt_hash, nls_cp); |
9fbc5908 | 518 | |
14cae324 | 519 | rc = crypto_shash_setkey(ses->server->secmech.hmacmd5, nt_hash, |
307fbd31 | 520 | CIFS_NTHASH_SIZE); |
14cae324 | 521 | if (rc) { |
f96637be | 522 | cifs_dbg(VFS, "%s: Could not set NT Hash as a key\n", __func__); |
14cae324 SP |
523 | return rc; |
524 | } | |
307fbd31 SP |
525 | |
526 | rc = crypto_shash_init(&ses->server->secmech.sdeschmacmd5->shash); | |
527 | if (rc) { | |
f96637be | 528 | cifs_dbg(VFS, "%s: could not init hmacmd5\n", __func__); |
307fbd31 SP |
529 | return rc; |
530 | } | |
a8ee0344 | 531 | |
fdf96a90 | 532 | /* convert ses->user_name to unicode */ |
04febabc | 533 | len = ses->user_name ? strlen(ses->user_name) : 0; |
1717ffc5 | 534 | user = kmalloc(2 + (len * 2), GFP_KERNEL); |
307fbd31 | 535 | if (user == NULL) { |
307fbd31 | 536 | rc = -ENOMEM; |
14cae324 | 537 | return rc; |
307fbd31 | 538 | } |
04febabc JL |
539 | |
540 | if (len) { | |
fdf96a90 | 541 | len = cifs_strtoUTF16(user, ses->user_name, len, nls_cp); |
04febabc JL |
542 | UniStrupr(user); |
543 | } else { | |
544 | memset(user, '\0', 2); | |
545 | } | |
307fbd31 | 546 | |
14cae324 | 547 | rc = crypto_shash_update(&ses->server->secmech.sdeschmacmd5->shash, |
307fbd31 | 548 | (char *)user, 2 * len); |
14cae324 SP |
549 | kfree(user); |
550 | if (rc) { | |
f96637be | 551 | cifs_dbg(VFS, "%s: Could not update with user\n", __func__); |
14cae324 SP |
552 | return rc; |
553 | } | |
a8ee0344 SF |
554 | |
555 | /* convert ses->domainName to unicode and uppercase */ | |
50c2f753 | 556 | if (ses->domainName) { |
1717ffc5 | 557 | len = strlen(ses->domainName); |
a8ee0344 | 558 | |
50c2f753 | 559 | domain = kmalloc(2 + (len * 2), GFP_KERNEL); |
307fbd31 | 560 | if (domain == NULL) { |
307fbd31 | 561 | rc = -ENOMEM; |
14cae324 | 562 | return rc; |
307fbd31 | 563 | } |
acbbb76a SF |
564 | len = cifs_strtoUTF16((__le16 *)domain, ses->domainName, len, |
565 | nls_cp); | |
14cae324 | 566 | rc = |
307fbd31 SP |
567 | crypto_shash_update(&ses->server->secmech.sdeschmacmd5->shash, |
568 | (char *)domain, 2 * len); | |
1717ffc5 | 569 | kfree(domain); |
14cae324 | 570 | if (rc) { |
f96637be JP |
571 | cifs_dbg(VFS, "%s: Could not update with domain\n", |
572 | __func__); | |
14cae324 SP |
573 | return rc; |
574 | } | |
8b7a4544 SF |
575 | } else { |
576 | /* We use ses->serverName if no domain name available */ | |
307fbd31 SP |
577 | len = strlen(ses->serverName); |
578 | ||
579 | server = kmalloc(2 + (len * 2), GFP_KERNEL); | |
580 | if (server == NULL) { | |
307fbd31 | 581 | rc = -ENOMEM; |
14cae324 | 582 | return rc; |
307fbd31 | 583 | } |
acbbb76a | 584 | len = cifs_strtoUTF16((__le16 *)server, ses->serverName, len, |
307fbd31 | 585 | nls_cp); |
14cae324 | 586 | rc = |
307fbd31 SP |
587 | crypto_shash_update(&ses->server->secmech.sdeschmacmd5->shash, |
588 | (char *)server, 2 * len); | |
589 | kfree(server); | |
14cae324 | 590 | if (rc) { |
f96637be JP |
591 | cifs_dbg(VFS, "%s: Could not update with server\n", |
592 | __func__); | |
14cae324 SP |
593 | return rc; |
594 | } | |
1717ffc5 | 595 | } |
307fbd31 SP |
596 | |
597 | rc = crypto_shash_final(&ses->server->secmech.sdeschmacmd5->shash, | |
d3686d54 | 598 | ntlmv2_hash); |
14cae324 | 599 | if (rc) |
f96637be | 600 | cifs_dbg(VFS, "%s: Could not generate md5 hash\n", __func__); |
307fbd31 | 601 | |
307fbd31 SP |
602 | return rc; |
603 | } | |
604 | ||
605 | static int | |
96daf2b0 | 606 | CalcNTLMv2_response(const struct cifs_ses *ses, char *ntlmv2_hash) |
307fbd31 SP |
607 | { |
608 | int rc; | |
2c957ddf TG |
609 | struct ntlmv2_resp *ntlmv2 = (struct ntlmv2_resp *) |
610 | (ses->auth_key.response + CIFS_SESS_KEY_SIZE); | |
611 | unsigned int hash_len; | |
612 | ||
613 | /* The MD5 hash starts at challenge_key.key */ | |
614 | hash_len = ses->auth_key.len - (CIFS_SESS_KEY_SIZE + | |
615 | offsetof(struct ntlmv2_resp, challenge.key[0])); | |
307fbd31 SP |
616 | |
617 | if (!ses->server->secmech.sdeschmacmd5) { | |
f96637be | 618 | cifs_dbg(VFS, "%s: can't generate ntlmv2 hash\n", __func__); |
307fbd31 SP |
619 | return -1; |
620 | } | |
621 | ||
14cae324 | 622 | rc = crypto_shash_setkey(ses->server->secmech.hmacmd5, |
2c957ddf | 623 | ntlmv2_hash, CIFS_HMAC_MD5_HASH_SIZE); |
14cae324 | 624 | if (rc) { |
f96637be JP |
625 | cifs_dbg(VFS, "%s: Could not set NTLMV2 Hash as a key\n", |
626 | __func__); | |
14cae324 SP |
627 | return rc; |
628 | } | |
307fbd31 SP |
629 | |
630 | rc = crypto_shash_init(&ses->server->secmech.sdeschmacmd5->shash); | |
631 | if (rc) { | |
f96637be | 632 | cifs_dbg(VFS, "%s: could not init hmacmd5\n", __func__); |
307fbd31 SP |
633 | return rc; |
634 | } | |
635 | ||
3f618223 | 636 | if (ses->server->negflavor == CIFS_NEGFLAVOR_EXTENDED) |
2c957ddf TG |
637 | memcpy(ntlmv2->challenge.key, |
638 | ses->ntlmssp->cryptkey, CIFS_SERVER_CHALLENGE_SIZE); | |
d3ba50b1 | 639 | else |
2c957ddf TG |
640 | memcpy(ntlmv2->challenge.key, |
641 | ses->server->cryptkey, CIFS_SERVER_CHALLENGE_SIZE); | |
14cae324 | 642 | rc = crypto_shash_update(&ses->server->secmech.sdeschmacmd5->shash, |
2c957ddf | 643 | ntlmv2->challenge.key, hash_len); |
14cae324 | 644 | if (rc) { |
f96637be | 645 | cifs_dbg(VFS, "%s: Could not update with response\n", __func__); |
14cae324 SP |
646 | return rc; |
647 | } | |
307fbd31 | 648 | |
2c957ddf | 649 | /* Note that the MD5 digest over writes anon.challenge_key.key */ |
307fbd31 | 650 | rc = crypto_shash_final(&ses->server->secmech.sdeschmacmd5->shash, |
2c957ddf | 651 | ntlmv2->ntlmv2_hash); |
14cae324 | 652 | if (rc) |
f96637be | 653 | cifs_dbg(VFS, "%s: Could not generate md5 hash\n", __func__); |
9fbc5908 SF |
654 | |
655 | return rc; | |
656 | } | |
657 | ||
95dc8dd1 SF |
658 | static int crypto_hmacmd5_alloc(struct TCP_Server_Info *server) |
659 | { | |
ba482029 | 660 | int rc; |
95dc8dd1 SF |
661 | unsigned int size; |
662 | ||
663 | /* check if already allocated */ | |
664 | if (server->secmech.sdeschmacmd5) | |
665 | return 0; | |
666 | ||
667 | server->secmech.hmacmd5 = crypto_alloc_shash("hmac(md5)", 0, 0); | |
668 | if (IS_ERR(server->secmech.hmacmd5)) { | |
669 | cifs_dbg(VFS, "could not allocate crypto hmacmd5\n"); | |
ba482029 JL |
670 | rc = PTR_ERR(server->secmech.hmacmd5); |
671 | server->secmech.hmacmd5 = NULL; | |
672 | return rc; | |
95dc8dd1 SF |
673 | } |
674 | ||
675 | size = sizeof(struct shash_desc) + | |
676 | crypto_shash_descsize(server->secmech.hmacmd5); | |
677 | server->secmech.sdeschmacmd5 = kmalloc(size, GFP_KERNEL); | |
678 | if (!server->secmech.sdeschmacmd5) { | |
679 | crypto_free_shash(server->secmech.hmacmd5); | |
680 | server->secmech.hmacmd5 = NULL; | |
681 | return -ENOMEM; | |
682 | } | |
683 | server->secmech.sdeschmacmd5->shash.tfm = server->secmech.hmacmd5; | |
684 | server->secmech.sdeschmacmd5->shash.flags = 0x0; | |
685 | ||
686 | return 0; | |
687 | } | |
307fbd31 | 688 | |
2b149f11 | 689 | int |
96daf2b0 | 690 | setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp) |
9fbc5908 | 691 | { |
c8e56f1f | 692 | int rc; |
21e73393 | 693 | int baselen; |
d3686d54 | 694 | unsigned int tilen; |
2c957ddf | 695 | struct ntlmv2_resp *ntlmv2; |
d3686d54 SP |
696 | char ntlmv2_hash[16]; |
697 | unsigned char *tiblob = NULL; /* target info blob */ | |
98ce94c8 | 698 | __le64 rsp_timestamp; |
6d027cfd | 699 | |
3f618223 | 700 | if (ses->server->negflavor == CIFS_NEGFLAVOR_EXTENDED) { |
2b149f11 | 701 | if (!ses->domainName) { |
f7c5445a | 702 | rc = find_domain_name(ses, nls_cp); |
2b149f11 | 703 | if (rc) { |
f96637be JP |
704 | cifs_dbg(VFS, "error %d finding domain name\n", |
705 | rc); | |
2b149f11 SP |
706 | goto setup_ntlmv2_rsp_ret; |
707 | } | |
708 | } | |
709 | } else { | |
9daa42e2 | 710 | rc = build_avpair_blob(ses, nls_cp); |
2b149f11 | 711 | if (rc) { |
f96637be | 712 | cifs_dbg(VFS, "error %d building av pair blob\n", rc); |
d3686d54 | 713 | goto setup_ntlmv2_rsp_ret; |
2b149f11 SP |
714 | } |
715 | } | |
a8ee0344 | 716 | |
98ce94c8 PS |
717 | /* Must be within 5 minutes of the server (or in range +/-2h |
718 | * in case of Mac OS X), so simply carry over server timestamp | |
719 | * (as Windows 7 does) | |
720 | */ | |
721 | rsp_timestamp = find_timestamp(ses); | |
722 | ||
21e73393 | 723 | baselen = CIFS_SESS_KEY_SIZE + sizeof(struct ntlmv2_resp); |
d3686d54 SP |
724 | tilen = ses->auth_key.len; |
725 | tiblob = ses->auth_key.response; | |
726 | ||
727 | ses->auth_key.response = kmalloc(baselen + tilen, GFP_KERNEL); | |
21e73393 | 728 | if (!ses->auth_key.response) { |
4b550af5 | 729 | rc = -ENOMEM; |
d3686d54 | 730 | ses->auth_key.len = 0; |
21e73393 SP |
731 | goto setup_ntlmv2_rsp_ret; |
732 | } | |
d3686d54 | 733 | ses->auth_key.len += baselen; |
21e73393 | 734 | |
2c957ddf | 735 | ntlmv2 = (struct ntlmv2_resp *) |
21e73393 | 736 | (ses->auth_key.response + CIFS_SESS_KEY_SIZE); |
2c957ddf TG |
737 | ntlmv2->blob_signature = cpu_to_le32(0x00000101); |
738 | ntlmv2->reserved = 0; | |
98ce94c8 PS |
739 | ntlmv2->time = rsp_timestamp; |
740 | ||
2c957ddf TG |
741 | get_random_bytes(&ntlmv2->client_chal, sizeof(ntlmv2->client_chal)); |
742 | ntlmv2->reserved2 = 0; | |
21e73393 | 743 | |
d3686d54 | 744 | memcpy(ses->auth_key.response + baselen, tiblob, tilen); |
21e73393 | 745 | |
bd975d1e RV |
746 | mutex_lock(&ses->server->srv_mutex); |
747 | ||
95dc8dd1 SF |
748 | rc = crypto_hmacmd5_alloc(ses->server); |
749 | if (rc) { | |
750 | cifs_dbg(VFS, "could not crypto alloc hmacmd5 rc %d\n", rc); | |
bd975d1e | 751 | goto unlock; |
95dc8dd1 SF |
752 | } |
753 | ||
f7c5445a | 754 | /* calculate ntlmv2_hash */ |
d3686d54 | 755 | rc = calc_ntlmv2_hash(ses, ntlmv2_hash, nls_cp); |
2b149f11 | 756 | if (rc) { |
f96637be | 757 | cifs_dbg(VFS, "could not get v2 hash rc %d\n", rc); |
bd975d1e | 758 | goto unlock; |
2b149f11 | 759 | } |
f7c5445a SP |
760 | |
761 | /* calculate first part of the client response (CR1) */ | |
d3686d54 | 762 | rc = CalcNTLMv2_response(ses, ntlmv2_hash); |
307fbd31 | 763 | if (rc) { |
f96637be | 764 | cifs_dbg(VFS, "Could not calculate CR1 rc: %d\n", rc); |
bd975d1e | 765 | goto unlock; |
307fbd31 | 766 | } |
b609f06a | 767 | |
5d0d2882 | 768 | /* now calculate the session key for NTLMv2 */ |
14cae324 | 769 | rc = crypto_shash_setkey(ses->server->secmech.hmacmd5, |
d3686d54 | 770 | ntlmv2_hash, CIFS_HMAC_MD5_HASH_SIZE); |
14cae324 | 771 | if (rc) { |
f96637be JP |
772 | cifs_dbg(VFS, "%s: Could not set NTLMV2 Hash as a key\n", |
773 | __func__); | |
bd975d1e | 774 | goto unlock; |
14cae324 | 775 | } |
307fbd31 SP |
776 | |
777 | rc = crypto_shash_init(&ses->server->secmech.sdeschmacmd5->shash); | |
778 | if (rc) { | |
f96637be | 779 | cifs_dbg(VFS, "%s: Could not init hmacmd5\n", __func__); |
bd975d1e | 780 | goto unlock; |
307fbd31 SP |
781 | } |
782 | ||
14cae324 | 783 | rc = crypto_shash_update(&ses->server->secmech.sdeschmacmd5->shash, |
2c957ddf | 784 | ntlmv2->ntlmv2_hash, |
307fbd31 | 785 | CIFS_HMAC_MD5_HASH_SIZE); |
14cae324 | 786 | if (rc) { |
f96637be | 787 | cifs_dbg(VFS, "%s: Could not update with response\n", __func__); |
bd975d1e | 788 | goto unlock; |
14cae324 | 789 | } |
307fbd31 SP |
790 | |
791 | rc = crypto_shash_final(&ses->server->secmech.sdeschmacmd5->shash, | |
792 | ses->auth_key.response); | |
14cae324 | 793 | if (rc) |
f96637be | 794 | cifs_dbg(VFS, "%s: Could not generate md5 hash\n", __func__); |
2b149f11 | 795 | |
bd975d1e RV |
796 | unlock: |
797 | mutex_unlock(&ses->server->srv_mutex); | |
2b149f11 | 798 | setup_ntlmv2_rsp_ret: |
d3686d54 | 799 | kfree(tiblob); |
2b149f11 SP |
800 | |
801 | return rc; | |
6d027cfd SF |
802 | } |
803 | ||
d2b91521 | 804 | int |
96daf2b0 | 805 | calc_seckey(struct cifs_ses *ses) |
d2b91521 SP |
806 | { |
807 | int rc; | |
9651ddba | 808 | struct crypto_skcipher *tfm_arc4; |
d2b91521 | 809 | struct scatterlist sgin, sgout; |
9651ddba | 810 | struct skcipher_request *req; |
5f4b5569 SP |
811 | unsigned char *sec_key; |
812 | ||
813 | sec_key = kmalloc(CIFS_SESS_KEY_SIZE, GFP_KERNEL); | |
814 | if (sec_key == NULL) | |
815 | return -ENOMEM; | |
d2b91521 SP |
816 | |
817 | get_random_bytes(sec_key, CIFS_SESS_KEY_SIZE); | |
818 | ||
9651ddba | 819 | tfm_arc4 = crypto_alloc_skcipher("ecb(arc4)", 0, CRYPTO_ALG_ASYNC); |
7a8587e7 SP |
820 | if (IS_ERR(tfm_arc4)) { |
821 | rc = PTR_ERR(tfm_arc4); | |
f96637be | 822 | cifs_dbg(VFS, "could not allocate crypto API arc4\n"); |
5f4b5569 | 823 | goto out; |
d2b91521 SP |
824 | } |
825 | ||
9651ddba | 826 | rc = crypto_skcipher_setkey(tfm_arc4, ses->auth_key.response, |
d2b91521 | 827 | CIFS_SESS_KEY_SIZE); |
14cae324 | 828 | if (rc) { |
f96637be JP |
829 | cifs_dbg(VFS, "%s: Could not set response as a key\n", |
830 | __func__); | |
9651ddba HX |
831 | goto out_free_cipher; |
832 | } | |
833 | ||
834 | req = skcipher_request_alloc(tfm_arc4, GFP_KERNEL); | |
835 | if (!req) { | |
836 | rc = -ENOMEM; | |
837 | cifs_dbg(VFS, "could not allocate crypto API arc4 request\n"); | |
838 | goto out_free_cipher; | |
14cae324 | 839 | } |
d2b91521 SP |
840 | |
841 | sg_init_one(&sgin, sec_key, CIFS_SESS_KEY_SIZE); | |
d3686d54 | 842 | sg_init_one(&sgout, ses->ntlmssp->ciphertext, CIFS_CPHTXT_SIZE); |
d2b91521 | 843 | |
9651ddba HX |
844 | skcipher_request_set_callback(req, 0, NULL, NULL); |
845 | skcipher_request_set_crypt(req, &sgin, &sgout, CIFS_CPHTXT_SIZE, NULL); | |
846 | ||
847 | rc = crypto_skcipher_encrypt(req); | |
848 | skcipher_request_free(req); | |
d2b91521 | 849 | if (rc) { |
f96637be | 850 | cifs_dbg(VFS, "could not encrypt session key rc: %d\n", rc); |
9651ddba | 851 | goto out_free_cipher; |
d2b91521 SP |
852 | } |
853 | ||
854 | /* make secondary_key/nonce as session key */ | |
855 | memcpy(ses->auth_key.response, sec_key, CIFS_SESS_KEY_SIZE); | |
856 | /* and make len as that of session key only */ | |
857 | ses->auth_key.len = CIFS_SESS_KEY_SIZE; | |
858 | ||
9651ddba HX |
859 | out_free_cipher: |
860 | crypto_free_skcipher(tfm_arc4); | |
5f4b5569 SP |
861 | out: |
862 | kfree(sec_key); | |
14cae324 | 863 | return rc; |
d2b91521 SP |
864 | } |
865 | ||
866 | void | |
867 | cifs_crypto_shash_release(struct TCP_Server_Info *server) | |
868 | { | |
95dc8dd1 | 869 | if (server->secmech.cmacaes) { |
429b46f4 | 870 | crypto_free_shash(server->secmech.cmacaes); |
95dc8dd1 SF |
871 | server->secmech.cmacaes = NULL; |
872 | } | |
429b46f4 | 873 | |
95dc8dd1 | 874 | if (server->secmech.hmacsha256) { |
3c1bf7e4 | 875 | crypto_free_shash(server->secmech.hmacsha256); |
95dc8dd1 SF |
876 | server->secmech.hmacsha256 = NULL; |
877 | } | |
3c1bf7e4 | 878 | |
95dc8dd1 | 879 | if (server->secmech.md5) { |
d2b91521 | 880 | crypto_free_shash(server->secmech.md5); |
95dc8dd1 SF |
881 | server->secmech.md5 = NULL; |
882 | } | |
d2b91521 | 883 | |
95dc8dd1 | 884 | if (server->secmech.hmacmd5) { |
d2b91521 | 885 | crypto_free_shash(server->secmech.hmacmd5); |
95dc8dd1 SF |
886 | server->secmech.hmacmd5 = NULL; |
887 | } | |
d2b91521 | 888 | |
429b46f4 | 889 | kfree(server->secmech.sdesccmacaes); |
95dc8dd1 | 890 | server->secmech.sdesccmacaes = NULL; |
3c1bf7e4 | 891 | kfree(server->secmech.sdeschmacsha256); |
95dc8dd1 | 892 | server->secmech.sdeschmacsha256 = NULL; |
d2b91521 | 893 | kfree(server->secmech.sdeschmacmd5); |
95dc8dd1 | 894 | server->secmech.sdeschmacmd5 = NULL; |
d2b91521 | 895 | kfree(server->secmech.sdescmd5); |
95dc8dd1 | 896 | server->secmech.sdescmd5 = NULL; |
d2b91521 | 897 | } |