r26272: Remove global_loadparm in some more places.
[jelmer/samba4-debian.git] / source / rpc_server / samr / samr_password.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    samr server password set/change handling
5
6    Copyright (C) Andrew Tridgell 2004
7    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
8    
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 3 of the License, or
12    (at your option) any later version.
13    
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18    
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "includes.h"
24 #include "rpc_server/dcerpc_server.h"
25 #include "rpc_server/common/common.h"
26 #include "rpc_server/samr/dcesrv_samr.h"
27 #include "system/time.h"
28 #include "lib/crypto/crypto.h"
29 #include "dsdb/common/flags.h"
30 #include "libcli/ldap/ldap.h"
31 #include "dsdb/samdb/samdb.h"
32 #include "auth/auth.h"
33 #include "rpc_server/samr/proto.h"
34 #include "libcli/auth/libcli_auth.h"
35 #include "util/util_ldb.h"
36 #include "param/param.h"
37
38 /* 
39   samr_ChangePasswordUser 
40 */
41 NTSTATUS dcesrv_samr_ChangePasswordUser(struct dcesrv_call_state *dce_call, 
42                                         TALLOC_CTX *mem_ctx,
43                                         struct samr_ChangePasswordUser *r)
44 {
45         struct dcesrv_handle *h;
46         struct samr_account_state *a_state;
47         struct ldb_context *sam_ctx;
48         struct ldb_message **res, *msg;
49         int ret;
50         struct samr_Password new_lmPwdHash, new_ntPwdHash, checkHash;
51         struct samr_Password *lm_pwd, *nt_pwd;
52         NTSTATUS status = NT_STATUS_OK;
53         const char * const attrs[] = { "dBCSPwd", "unicodePwd" , NULL };
54
55         DCESRV_PULL_HANDLE(h, r->in.user_handle, SAMR_HANDLE_USER);
56
57         a_state = h->data;
58
59         /* basic sanity checking on parameters.  Do this before any database ops */
60         if (!r->in.lm_present || !r->in.nt_present ||
61             !r->in.old_lm_crypted || !r->in.new_lm_crypted ||
62             !r->in.old_nt_crypted || !r->in.new_nt_crypted) {
63                 /* we should really handle a change with lm not
64                    present */
65                 return NT_STATUS_INVALID_PARAMETER_MIX;
66         }
67
68         /* To change a password we need to open as system */
69         sam_ctx = samdb_connect(mem_ctx, global_loadparm, system_session(mem_ctx, global_loadparm));
70         if (sam_ctx == NULL) {
71                 return NT_STATUS_INVALID_SYSTEM_SERVICE;
72         }
73
74         ret = ldb_transaction_start(sam_ctx);
75         if (ret) {
76                 DEBUG(1, ("Failed to start transaction: %s\n", ldb_errstring(sam_ctx)));
77                 return NT_STATUS_TRANSACTION_ABORTED;
78         }
79
80         /* fetch the old hashes */
81         ret = gendb_search_dn(sam_ctx, mem_ctx,
82                               a_state->account_dn, &res, attrs);
83         if (ret != 1) {
84                 ldb_transaction_cancel(sam_ctx);
85                 return NT_STATUS_WRONG_PASSWORD;
86         }
87         msg = res[0];
88
89         status = samdb_result_passwords(mem_ctx, msg, &lm_pwd, &nt_pwd);
90         if (!NT_STATUS_IS_OK(status) || !lm_pwd || !nt_pwd) {
91                 ldb_transaction_cancel(sam_ctx);
92                 return NT_STATUS_WRONG_PASSWORD;
93         }
94
95         /* decrypt and check the new lm hash */
96         D_P16(lm_pwd->hash, r->in.new_lm_crypted->hash, new_lmPwdHash.hash);
97         D_P16(new_lmPwdHash.hash, r->in.old_lm_crypted->hash, checkHash.hash);
98         if (memcmp(checkHash.hash, lm_pwd, 16) != 0) {
99                 ldb_transaction_cancel(sam_ctx);
100                 return NT_STATUS_WRONG_PASSWORD;
101         }
102
103         /* decrypt and check the new nt hash */
104         D_P16(nt_pwd->hash, r->in.new_nt_crypted->hash, new_ntPwdHash.hash);
105         D_P16(new_ntPwdHash.hash, r->in.old_nt_crypted->hash, checkHash.hash);
106         if (memcmp(checkHash.hash, nt_pwd, 16) != 0) {
107                 ldb_transaction_cancel(sam_ctx);
108                 return NT_STATUS_WRONG_PASSWORD;
109         }
110         
111         /* The NT Cross is not required by Win2k3 R2, but if present
112            check the nt cross hash */
113         if (r->in.cross1_present && r->in.nt_cross) {
114                 D_P16(lm_pwd->hash, r->in.nt_cross->hash, checkHash.hash);
115                 if (memcmp(checkHash.hash, new_ntPwdHash.hash, 16) != 0) {
116                         ldb_transaction_cancel(sam_ctx);
117                         return NT_STATUS_WRONG_PASSWORD;
118                 }
119         }
120
121         /* The LM Cross is not required by Win2k3 R2, but if present
122            check the lm cross hash */
123         if (r->in.cross2_present && r->in.lm_cross) {
124                 D_P16(nt_pwd->hash, r->in.lm_cross->hash, checkHash.hash);
125                 if (memcmp(checkHash.hash, new_lmPwdHash.hash, 16) != 0) {
126                         ldb_transaction_cancel(sam_ctx);
127                         return NT_STATUS_WRONG_PASSWORD;
128                 }
129         }
130
131         msg = ldb_msg_new(mem_ctx);
132         if (msg == NULL) {
133                 ldb_transaction_cancel(sam_ctx);
134                 return NT_STATUS_NO_MEMORY;
135         }
136
137         msg->dn = ldb_dn_copy(msg, a_state->account_dn);
138         if (!msg->dn) {
139                 ldb_transaction_cancel(sam_ctx);
140                 return NT_STATUS_NO_MEMORY;
141         }
142
143         /* setup password modify mods on the user DN specified.  This may fail
144          * due to password policies.  */
145         status = samdb_set_password(sam_ctx, mem_ctx,
146                                     a_state->account_dn, a_state->domain_state->domain_dn,
147                                     msg, NULL, &new_lmPwdHash, &new_ntPwdHash, 
148                                     true, /* this is a user password change */
149                                     NULL,
150                                     NULL);
151         if (!NT_STATUS_IS_OK(status)) {
152                 ldb_transaction_cancel(sam_ctx);
153                 return status;
154         }
155
156         /* The above call only setup the modifications, this actually
157          * makes the write to the database. */
158         ret = samdb_replace(sam_ctx, mem_ctx, msg);
159         if (ret != 0) {
160                 DEBUG(2,("Failed to modify record to change password on %s: %s\n",
161                          ldb_dn_get_linearized(a_state->account_dn),
162                          ldb_errstring(sam_ctx)));
163                 ldb_transaction_cancel(sam_ctx);
164                 return NT_STATUS_INTERNAL_DB_CORRUPTION;
165         }
166
167         /* And this confirms it in a transaction commit */
168         ret = ldb_transaction_commit(sam_ctx);
169         if (ret != 0) {
170                 DEBUG(1,("Failed to commit transaction to change password on %s: %s\n",
171                          ldb_dn_get_linearized(a_state->account_dn),
172                          ldb_errstring(sam_ctx)));
173                 return NT_STATUS_TRANSACTION_ABORTED;
174         }
175
176         return NT_STATUS_OK;
177 }
178
179 /* 
180   samr_OemChangePasswordUser2 
181 */
182 NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
183                                      struct samr_OemChangePasswordUser2 *r)
184 {
185         NTSTATUS status;
186         char new_pass[512];
187         uint32_t new_pass_len;
188         struct samr_CryptPassword *pwbuf = r->in.password;
189         struct ldb_context *sam_ctx;
190         struct ldb_dn *user_dn;
191         int ret;
192         struct ldb_message **res, *mod;
193         const char * const attrs[] = { "objectSid", "dBCSPwd", NULL };
194         struct samr_Password *lm_pwd;
195         DATA_BLOB lm_pwd_blob;
196         uint8_t new_lm_hash[16];
197         struct samr_Password lm_verifier;
198
199         if (pwbuf == NULL) {
200                 return NT_STATUS_INVALID_PARAMETER;
201         }
202
203         if (r->in.hash == NULL) {
204                 return NT_STATUS_INVALID_PARAMETER;
205         }
206
207         /* To change a password we need to open as system */
208         sam_ctx = samdb_connect(mem_ctx, global_loadparm, system_session(mem_ctx, global_loadparm));
209         if (sam_ctx == NULL) {
210                 return NT_STATUS_INVALID_SYSTEM_SERVICE;
211         }
212
213         ret = ldb_transaction_start(sam_ctx);
214         if (ret) {
215                 DEBUG(1, ("Failed to start transaction: %s\n", ldb_errstring(sam_ctx)));
216                 return NT_STATUS_TRANSACTION_ABORTED;
217         }
218
219         /* we need the users dn and the domain dn (derived from the
220            user SID). We also need the current lm password hash in
221            order to decrypt the incoming password */
222         ret = gendb_search(sam_ctx, 
223                            mem_ctx, NULL, &res, attrs,
224                            "(&(sAMAccountName=%s)(objectclass=user))",
225                            r->in.account->string);
226         if (ret != 1) {
227                 ldb_transaction_cancel(sam_ctx);
228                 /* Don't give the game away:  (don't allow anonymous users to prove the existance of usernames) */
229                 return NT_STATUS_WRONG_PASSWORD;
230         }
231
232         user_dn = res[0]->dn;
233
234         status = samdb_result_passwords(mem_ctx, res[0], &lm_pwd, NULL);
235         if (!NT_STATUS_IS_OK(status) || !lm_pwd) {
236                 ldb_transaction_cancel(sam_ctx);
237                 return NT_STATUS_WRONG_PASSWORD;
238         }
239
240         /* decrypt the password we have been given */
241         lm_pwd_blob = data_blob(lm_pwd->hash, sizeof(lm_pwd->hash)); 
242         arcfour_crypt_blob(pwbuf->data, 516, &lm_pwd_blob);
243         data_blob_free(&lm_pwd_blob);
244         
245         if (!decode_pw_buffer(pwbuf->data, new_pass, sizeof(new_pass),
246                               &new_pass_len, STR_ASCII)) {
247                 ldb_transaction_cancel(sam_ctx);
248                 DEBUG(3,("samr: failed to decode password buffer\n"));
249                 return NT_STATUS_WRONG_PASSWORD;
250         }
251
252         /* check LM verifier */
253         if (lm_pwd == NULL) {
254                 ldb_transaction_cancel(sam_ctx);
255                 return NT_STATUS_WRONG_PASSWORD;
256         }
257
258         E_deshash(new_pass, new_lm_hash);
259         E_old_pw_hash(new_lm_hash, lm_pwd->hash, lm_verifier.hash);
260         if (memcmp(lm_verifier.hash, r->in.hash->hash, 16) != 0) {
261                 ldb_transaction_cancel(sam_ctx);
262                 return NT_STATUS_WRONG_PASSWORD;
263         }
264
265         mod = ldb_msg_new(mem_ctx);
266         if (mod == NULL) {
267                 ldb_transaction_cancel(sam_ctx);
268                 return NT_STATUS_NO_MEMORY;
269         }
270
271         mod->dn = ldb_dn_copy(mod, user_dn);
272         if (!mod->dn) {
273                 ldb_transaction_cancel(sam_ctx);
274                 return NT_STATUS_NO_MEMORY;
275         }
276
277         /* set the password on the user DN specified.  This may fail
278          * due to password policies */
279         status = samdb_set_password(sam_ctx, mem_ctx,
280                                     user_dn, NULL, 
281                                     mod, new_pass, 
282                                     NULL, NULL,
283                                     true, /* this is a user password change */
284                                     NULL, 
285                                     NULL);
286         if (!NT_STATUS_IS_OK(status)) {
287                 ldb_transaction_cancel(sam_ctx);
288                 return status;
289         }
290
291         /* The above call only setup the modifications, this actually
292          * makes the write to the database. */
293         ret = samdb_replace(sam_ctx, mem_ctx, mod);
294         if (ret != 0) {
295                 DEBUG(2,("Failed to modify record to change password on %s: %s\n",
296                          ldb_dn_get_linearized(user_dn),
297                          ldb_errstring(sam_ctx)));
298                 ldb_transaction_cancel(sam_ctx);
299                 return NT_STATUS_INTERNAL_DB_CORRUPTION;
300         }
301
302         /* And this confirms it in a transaction commit */
303         ret = ldb_transaction_commit(sam_ctx);
304         if (ret != 0) {
305                 DEBUG(1,("Failed to commit transaction to change password on %s: %s\n",
306                          ldb_dn_get_linearized(user_dn),
307                          ldb_errstring(sam_ctx)));
308                 return NT_STATUS_TRANSACTION_ABORTED;
309         }
310
311         return NT_STATUS_OK;
312 }
313
314
315 /* 
316   samr_ChangePasswordUser3 
317 */
318 NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call, 
319                                   TALLOC_CTX *mem_ctx,
320                                   struct samr_ChangePasswordUser3 *r)
321 {       
322         NTSTATUS status;
323         char new_pass[512];
324         uint32_t new_pass_len;
325         struct ldb_context *sam_ctx = NULL;
326         struct ldb_dn *user_dn;
327         int ret;
328         struct ldb_message **res, *mod;
329         const char * const attrs[] = { "unicodePwd", "dBCSPwd", NULL };
330         struct samr_Password *nt_pwd, *lm_pwd;
331         DATA_BLOB nt_pwd_blob;
332         struct samr_DomInfo1 *dominfo = NULL;
333         struct samr_ChangeReject *reject = NULL;
334         enum samr_RejectReason reason = SAMR_REJECT_OTHER;
335         uint8_t new_nt_hash[16], new_lm_hash[16];
336         struct samr_Password nt_verifier, lm_verifier;
337
338         ZERO_STRUCT(r->out);
339
340         if (r->in.nt_password == NULL ||
341             r->in.nt_verifier == NULL) {
342                 return NT_STATUS_INVALID_PARAMETER;
343         }
344
345         /* To change a password we need to open as system */
346         sam_ctx = samdb_connect(mem_ctx, global_loadparm, system_session(mem_ctx, global_loadparm));
347         if (sam_ctx == NULL) {
348                 return NT_STATUS_INVALID_SYSTEM_SERVICE;
349         }
350
351         ret = ldb_transaction_start(sam_ctx);
352         if (ret) {
353                 talloc_free(sam_ctx);
354                 DEBUG(1, ("Failed to start transaction: %s\n", ldb_errstring(sam_ctx)));
355                 return NT_STATUS_TRANSACTION_ABORTED;
356         }
357
358         /* we need the users dn and the domain dn (derived from the
359            user SID). We also need the current lm and nt password hashes
360            in order to decrypt the incoming passwords */
361         ret = gendb_search(sam_ctx, 
362                            mem_ctx, NULL, &res, attrs,
363                            "(&(sAMAccountName=%s)(objectclass=user))",
364                            r->in.account->string);
365         if (ret != 1) {
366                 /* Don't give the game away:  (don't allow anonymous users to prove the existance of usernames) */
367                 status = NT_STATUS_WRONG_PASSWORD;
368                 goto failed;
369         }
370
371         user_dn = res[0]->dn;
372
373         status = samdb_result_passwords(mem_ctx, res[0], &lm_pwd, &nt_pwd);
374         if (!NT_STATUS_IS_OK(status) ) {
375                 goto failed;
376         }
377
378         if (!nt_pwd) {
379                 status = NT_STATUS_WRONG_PASSWORD;
380                 goto failed;
381         }
382
383         /* decrypt the password we have been given */
384         nt_pwd_blob = data_blob(nt_pwd->hash, sizeof(nt_pwd->hash));
385         arcfour_crypt_blob(r->in.nt_password->data, 516, &nt_pwd_blob);
386         data_blob_free(&nt_pwd_blob);
387
388         if (!decode_pw_buffer(r->in.nt_password->data, new_pass, sizeof(new_pass),
389                               &new_pass_len, STR_UNICODE)) {
390                 DEBUG(3,("samr: failed to decode password buffer\n"));
391                 status = NT_STATUS_WRONG_PASSWORD;
392                 goto failed;
393         }
394
395         if (r->in.nt_verifier == NULL) {
396                 status = NT_STATUS_WRONG_PASSWORD;
397                 goto failed;
398         }
399
400         /* check NT verifier */
401         E_md4hash(new_pass, new_nt_hash);
402         E_old_pw_hash(new_nt_hash, nt_pwd->hash, nt_verifier.hash);
403         if (memcmp(nt_verifier.hash, r->in.nt_verifier->hash, 16) != 0) {
404                 status = NT_STATUS_WRONG_PASSWORD;
405                 goto failed;
406         }
407
408         /* check LM verifier */
409         if (lm_pwd && r->in.lm_verifier != NULL) {
410                 E_deshash(new_pass, new_lm_hash);
411                 E_old_pw_hash(new_nt_hash, lm_pwd->hash, lm_verifier.hash);
412                 if (memcmp(lm_verifier.hash, r->in.lm_verifier->hash, 16) != 0) {
413                         status = NT_STATUS_WRONG_PASSWORD;
414                         goto failed;
415                 }
416         }
417
418
419         mod = ldb_msg_new(mem_ctx);
420         if (mod == NULL) {
421                 return NT_STATUS_NO_MEMORY;
422         }
423
424         mod->dn = ldb_dn_copy(mod, user_dn);
425         if (!mod->dn) {
426                 status = NT_STATUS_NO_MEMORY;
427                 goto failed;
428         }
429
430         /* set the password on the user DN specified.  This may fail
431          * due to password policies */
432         status = samdb_set_password(sam_ctx, mem_ctx,
433                                     user_dn, NULL, 
434                                     mod, new_pass, 
435                                     NULL, NULL,
436                                     true, /* this is a user password change */
437                                     &reason, 
438                                     &dominfo);
439         if (!NT_STATUS_IS_OK(status)) {
440                 goto failed;
441         }
442
443         /* The above call only setup the modifications, this actually
444          * makes the write to the database. */
445         ret = samdb_replace(sam_ctx, mem_ctx, mod);
446         if (ret != 0) {
447                 DEBUG(2,("samdb_replace failed to change password for %s: %s\n",
448                          ldb_dn_get_linearized(user_dn),
449                          ldb_errstring(sam_ctx)));
450                 status = NT_STATUS_UNSUCCESSFUL;
451                 goto failed;
452         }
453
454         /* And this confirms it in a transaction commit */
455         ret = ldb_transaction_commit(sam_ctx);
456         if (ret != 0) {
457                 DEBUG(1,("Failed to commit transaction to change password on %s: %s\n",
458                          ldb_dn_get_linearized(user_dn),
459                          ldb_errstring(sam_ctx)));
460                 status = NT_STATUS_TRANSACTION_ABORTED;
461                 goto failed;
462         }
463
464         return NT_STATUS_OK;
465
466 failed:
467         ldb_transaction_cancel(sam_ctx);
468         talloc_free(sam_ctx);
469
470         reject = talloc(mem_ctx, struct samr_ChangeReject);
471         r->out.dominfo = dominfo;
472         r->out.reject = reject;
473
474         if (reject == NULL) {
475                 return status;
476         }
477         ZERO_STRUCTP(reject);
478
479         reject->reason = reason;
480
481         return status;
482 }
483
484
485 /* 
486   samr_ChangePasswordUser2 
487
488   easy - just a subset of samr_ChangePasswordUser3
489 */
490 NTSTATUS dcesrv_samr_ChangePasswordUser2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
491                                   struct samr_ChangePasswordUser2 *r)
492 {
493         struct samr_ChangePasswordUser3 r2;
494
495         r2.in.server = r->in.server;
496         r2.in.account = r->in.account;
497         r2.in.nt_password = r->in.nt_password;
498         r2.in.nt_verifier = r->in.nt_verifier;
499         r2.in.lm_change = r->in.lm_change;
500         r2.in.lm_password = r->in.lm_password;
501         r2.in.lm_verifier = r->in.lm_verifier;
502         r2.in.password3 = NULL;
503
504         return dcesrv_samr_ChangePasswordUser3(dce_call, mem_ctx, &r2);
505 }
506
507
508 /*
509   set password via a samr_CryptPassword buffer
510   this will in the 'msg' with modify operations that will update the user
511   password when applied
512 */
513 NTSTATUS samr_set_password(struct dcesrv_call_state *dce_call,
514                            void *sam_ctx,
515                            struct ldb_dn *account_dn, struct ldb_dn *domain_dn,
516                            TALLOC_CTX *mem_ctx,
517                            struct ldb_message *msg, 
518                            struct samr_CryptPassword *pwbuf)
519 {
520         NTSTATUS nt_status;
521         char new_pass[512];
522         uint32_t new_pass_len;
523         DATA_BLOB session_key = data_blob(NULL, 0);
524
525         nt_status = dcesrv_fetch_session_key(dce_call->conn, &session_key);
526         if (!NT_STATUS_IS_OK(nt_status)) {
527                 return nt_status;
528         }
529
530         arcfour_crypt_blob(pwbuf->data, 516, &session_key);
531
532         if (!decode_pw_buffer(pwbuf->data, new_pass, sizeof(new_pass),
533                               &new_pass_len, STR_UNICODE)) {
534                 DEBUG(3,("samr: failed to decode password buffer\n"));
535                 return NT_STATUS_WRONG_PASSWORD;
536         }
537
538         /* set the password - samdb needs to know both the domain and user DNs,
539            so the domain password policy can be used */
540         return samdb_set_password(sam_ctx, mem_ctx,
541                                   account_dn, domain_dn, 
542                                   msg, new_pass, 
543                                   NULL, NULL,
544                                   false, /* This is a password set, not change */
545                                   NULL, NULL);
546 }
547
548
549 /*
550   set password via a samr_CryptPasswordEx buffer
551   this will in the 'msg' with modify operations that will update the user
552   password when applied
553 */
554 NTSTATUS samr_set_password_ex(struct dcesrv_call_state *dce_call,
555                               struct ldb_context *sam_ctx,
556                               struct ldb_dn *account_dn, struct ldb_dn *domain_dn,
557                               TALLOC_CTX *mem_ctx,
558                               struct ldb_message *msg, 
559                               struct samr_CryptPasswordEx *pwbuf)
560 {
561         NTSTATUS nt_status;
562         char new_pass[512];
563         uint32_t new_pass_len;
564         DATA_BLOB co_session_key;
565         DATA_BLOB session_key = data_blob(NULL, 0);
566         struct MD5Context ctx;
567
568         nt_status = dcesrv_fetch_session_key(dce_call->conn, &session_key);
569         if (!NT_STATUS_IS_OK(nt_status)) {
570                 return nt_status;
571         }
572
573         co_session_key = data_blob_talloc(mem_ctx, NULL, 16);
574         if (!co_session_key.data) {
575                 return NT_STATUS_NO_MEMORY;
576         }
577
578         MD5Init(&ctx);
579         MD5Update(&ctx, &pwbuf->data[516], 16);
580         MD5Update(&ctx, session_key.data, session_key.length);
581         MD5Final(co_session_key.data, &ctx);
582         
583         arcfour_crypt_blob(pwbuf->data, 516, &co_session_key);
584
585         if (!decode_pw_buffer(pwbuf->data, new_pass, sizeof(new_pass),
586                               &new_pass_len, STR_UNICODE)) {
587                 DEBUG(3,("samr: failed to decode password buffer\n"));
588                 return NT_STATUS_WRONG_PASSWORD;
589         }
590
591         /* set the password - samdb needs to know both the domain and user DNs,
592            so the domain password policy can be used */
593         return samdb_set_password(sam_ctx, mem_ctx,
594                                   account_dn, domain_dn, 
595                                   msg, new_pass, 
596                                   NULL, NULL,
597                                   false, /* This is a password set, not change */
598                                   NULL, NULL);
599 }
600
601