Sync with trunk revision r58045 to bring the corrections on configure.cmd and on...
[reactos.git] / dll / win32 / samsrv / database.c
1 /*
2 * PROJECT: Local Security Authority Server DLL
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: dll/win32/samsrv/database.c
5 * PURPOSE: SAM object database
6 * COPYRIGHT: Copyright 2012 Eric Kohl
7 */
8
9 /* INCLUDES ****************************************************************/
10
11 #include "samsrv.h"
12
13 WINE_DEFAULT_DEBUG_CHANNEL(samsrv);
14
15
16 /* GLOBALS *****************************************************************/
17
18 static HANDLE SamKeyHandle = NULL;
19
20
21 /* FUNCTIONS ***************************************************************/
22
23 static NTSTATUS
24 SampOpenSamKey(VOID)
25 {
26 OBJECT_ATTRIBUTES ObjectAttributes;
27 UNICODE_STRING KeyName;
28 NTSTATUS Status;
29
30 RtlInitUnicodeString(&KeyName,
31 L"\\Registry\\Machine\\SAM");
32
33 InitializeObjectAttributes(&ObjectAttributes,
34 &KeyName,
35 OBJ_CASE_INSENSITIVE,
36 NULL,
37 NULL);
38
39 Status = RtlpNtOpenKey(&SamKeyHandle,
40 KEY_READ | KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS,
41 &ObjectAttributes,
42 0);
43
44 return Status;
45 }
46
47
48 NTSTATUS
49 SampInitDatabase(VOID)
50 {
51 NTSTATUS Status;
52
53 TRACE("SampInitDatabase()\n");
54
55 Status = SampOpenSamKey();
56 if (!NT_SUCCESS(Status))
57 {
58 ERR("Failed to open the SAM key (Status: 0x%08lx)\n", Status);
59 return Status;
60 }
61
62 #if 0
63 if (!LsapIsDatabaseInstalled())
64 {
65 Status = LsapCreateDatabaseKeys();
66 if (!NT_SUCCESS(Status))
67 {
68 ERR("Failed to create the LSA database keys (Status: 0x%08lx)\n", Status);
69 return Status;
70 }
71
72 Status = LsapCreateDatabaseObjects();
73 if (!NT_SUCCESS(Status))
74 {
75 ERR("Failed to create the LSA database objects (Status: 0x%08lx)\n", Status);
76 return Status;
77 }
78 }
79 else
80 {
81 Status = LsapUpdateDatabase();
82 if (!NT_SUCCESS(Status))
83 {
84 ERR("Failed to update the LSA database (Status: 0x%08lx)\n", Status);
85 return Status;
86 }
87 }
88 #endif
89
90 TRACE("SampInitDatabase() done\n");
91
92 return STATUS_SUCCESS;
93 }
94
95
96 NTSTATUS
97 SampCreateDbObject(IN PSAM_DB_OBJECT ParentObject,
98 IN LPWSTR ContainerName,
99 IN LPWSTR ObjectName,
100 IN ULONG RelativeId,
101 IN SAM_DB_OBJECT_TYPE ObjectType,
102 IN ACCESS_MASK DesiredAccess,
103 OUT PSAM_DB_OBJECT *DbObject)
104 {
105 PSAM_DB_OBJECT NewObject;
106 OBJECT_ATTRIBUTES ObjectAttributes;
107 UNICODE_STRING KeyName;
108 HANDLE ParentKeyHandle;
109 HANDLE ContainerKeyHandle = NULL;
110 HANDLE ObjectKeyHandle = NULL;
111 HANDLE MembersKeyHandle = NULL;
112 NTSTATUS Status;
113
114 if (DbObject == NULL)
115 return STATUS_INVALID_PARAMETER;
116
117 if (ParentObject == NULL)
118 ParentKeyHandle = SamKeyHandle;
119 else
120 ParentKeyHandle = ParentObject->KeyHandle;
121
122 if (ContainerName != NULL)
123 {
124 /* Open the container key */
125 RtlInitUnicodeString(&KeyName,
126 ContainerName);
127
128 InitializeObjectAttributes(&ObjectAttributes,
129 &KeyName,
130 OBJ_CASE_INSENSITIVE,
131 ParentKeyHandle,
132 NULL);
133
134 Status = NtOpenKey(&ContainerKeyHandle,
135 KEY_ALL_ACCESS,
136 &ObjectAttributes);
137 if (!NT_SUCCESS(Status))
138 {
139 return Status;
140 }
141
142 /* Open the object key */
143 RtlInitUnicodeString(&KeyName,
144 ObjectName);
145
146 InitializeObjectAttributes(&ObjectAttributes,
147 &KeyName,
148 OBJ_CASE_INSENSITIVE,
149 ContainerKeyHandle,
150 NULL);
151
152 Status = NtCreateKey(&ObjectKeyHandle,
153 KEY_ALL_ACCESS,
154 &ObjectAttributes,
155 0,
156 NULL,
157 0,
158 NULL);
159
160 if ((ObjectType == SamDbAliasObject) ||
161 (ObjectType == SamDbGroupObject))
162 {
163 /* Open the object key */
164 RtlInitUnicodeString(&KeyName,
165 L"Members");
166
167 InitializeObjectAttributes(&ObjectAttributes,
168 &KeyName,
169 OBJ_CASE_INSENSITIVE | OBJ_OPENIF,
170 ContainerKeyHandle,
171 NULL);
172
173 Status = NtCreateKey(&MembersKeyHandle,
174 KEY_ALL_ACCESS,
175 &ObjectAttributes,
176 0,
177 NULL,
178 0,
179 NULL);
180 }
181
182 NtClose(ContainerKeyHandle);
183
184 if (!NT_SUCCESS(Status))
185 {
186 return Status;
187 }
188 }
189 else
190 {
191 RtlInitUnicodeString(&KeyName,
192 ObjectName);
193
194 InitializeObjectAttributes(&ObjectAttributes,
195 &KeyName,
196 OBJ_CASE_INSENSITIVE,
197 ParentKeyHandle,
198 NULL);
199
200 Status = NtCreateKey(&ObjectKeyHandle,
201 KEY_ALL_ACCESS,
202 &ObjectAttributes,
203 0,
204 NULL,
205 0,
206 NULL);
207 if (!NT_SUCCESS(Status))
208 {
209 return Status;
210 }
211 }
212
213 NewObject = RtlAllocateHeap(RtlGetProcessHeap(),
214 0,
215 sizeof(SAM_DB_OBJECT));
216 if (NewObject == NULL)
217 {
218 if (MembersKeyHandle != NULL)
219 NtClose(MembersKeyHandle);
220 NtClose(ObjectKeyHandle);
221 return STATUS_NO_MEMORY;
222 }
223
224 NewObject->Name = RtlAllocateHeap(RtlGetProcessHeap(),
225 0,
226 (wcslen(ObjectName) + 1) * sizeof(WCHAR));
227 if (NewObject->Name == NULL)
228 {
229 if (MembersKeyHandle != NULL)
230 NtClose(MembersKeyHandle);
231 NtClose(ObjectKeyHandle);
232 RtlFreeHeap(RtlGetProcessHeap(), 0, NewObject);
233 return STATUS_NO_MEMORY;
234 }
235
236 wcscpy(NewObject->Name, ObjectName);
237
238 NewObject->Signature = SAMP_DB_SIGNATURE;
239 NewObject->RefCount = 1;
240 NewObject->ObjectType = ObjectType;
241 NewObject->Access = DesiredAccess;
242 NewObject->KeyHandle = ObjectKeyHandle;
243 NewObject->MembersKeyHandle = MembersKeyHandle;
244 NewObject->RelativeId = RelativeId;
245 NewObject->ParentObject = ParentObject;
246
247 if (ParentObject != NULL)
248 ParentObject->RefCount++;
249
250 *DbObject = NewObject;
251
252 return STATUS_SUCCESS;
253 }
254
255
256 NTSTATUS
257 SampOpenDbObject(IN PSAM_DB_OBJECT ParentObject,
258 IN LPWSTR ContainerName,
259 IN LPWSTR ObjectName,
260 IN ULONG RelativeId,
261 IN SAM_DB_OBJECT_TYPE ObjectType,
262 IN ACCESS_MASK DesiredAccess,
263 OUT PSAM_DB_OBJECT *DbObject)
264 {
265 PSAM_DB_OBJECT NewObject;
266 OBJECT_ATTRIBUTES ObjectAttributes;
267 UNICODE_STRING KeyName;
268 HANDLE ParentKeyHandle;
269 HANDLE ContainerKeyHandle = NULL;
270 HANDLE ObjectKeyHandle = NULL;
271 HANDLE MembersKeyHandle = NULL;
272 NTSTATUS Status;
273
274 if (DbObject == NULL)
275 return STATUS_INVALID_PARAMETER;
276
277 if (ParentObject == NULL)
278 ParentKeyHandle = SamKeyHandle;
279 else
280 ParentKeyHandle = ParentObject->KeyHandle;
281
282 if (ContainerName != NULL)
283 {
284 /* Open the container key */
285 RtlInitUnicodeString(&KeyName,
286 ContainerName);
287
288 InitializeObjectAttributes(&ObjectAttributes,
289 &KeyName,
290 OBJ_CASE_INSENSITIVE,
291 ParentKeyHandle,
292 NULL);
293
294 Status = NtOpenKey(&ContainerKeyHandle,
295 KEY_ALL_ACCESS,
296 &ObjectAttributes);
297 if (!NT_SUCCESS(Status))
298 {
299 return Status;
300 }
301
302 /* Open the object key */
303 RtlInitUnicodeString(&KeyName,
304 ObjectName);
305
306 InitializeObjectAttributes(&ObjectAttributes,
307 &KeyName,
308 OBJ_CASE_INSENSITIVE,
309 ContainerKeyHandle,
310 NULL);
311
312 Status = NtOpenKey(&ObjectKeyHandle,
313 KEY_ALL_ACCESS,
314 &ObjectAttributes);
315
316 if ((ObjectType == SamDbAliasObject) ||
317 (ObjectType == SamDbGroupObject))
318 {
319 /* Open the object key */
320 RtlInitUnicodeString(&KeyName,
321 L"Members");
322
323 InitializeObjectAttributes(&ObjectAttributes,
324 &KeyName,
325 OBJ_CASE_INSENSITIVE | OBJ_OPENIF,
326 ContainerKeyHandle,
327 NULL);
328
329 Status = NtCreateKey(&MembersKeyHandle,
330 KEY_ALL_ACCESS,
331 &ObjectAttributes,
332 0,
333 NULL,
334 0,
335 NULL);
336 }
337
338 NtClose(ContainerKeyHandle);
339
340 if (!NT_SUCCESS(Status))
341 {
342 return Status;
343 }
344 }
345 else
346 {
347 /* Open the object key */
348 RtlInitUnicodeString(&KeyName,
349 ObjectName);
350
351 InitializeObjectAttributes(&ObjectAttributes,
352 &KeyName,
353 OBJ_CASE_INSENSITIVE,
354 ParentKeyHandle,
355 NULL);
356
357 Status = NtOpenKey(&ObjectKeyHandle,
358 KEY_ALL_ACCESS,
359 &ObjectAttributes);
360 if (!NT_SUCCESS(Status))
361 {
362 return Status;
363 }
364 }
365
366 NewObject = RtlAllocateHeap(RtlGetProcessHeap(),
367 0,
368 sizeof(SAM_DB_OBJECT));
369 if (NewObject == NULL)
370 {
371 if (MembersKeyHandle != NULL)
372 NtClose(MembersKeyHandle);
373 NtClose(ObjectKeyHandle);
374 return STATUS_NO_MEMORY;
375 }
376
377 NewObject->Name = RtlAllocateHeap(RtlGetProcessHeap(),
378 0,
379 (wcslen(ObjectName) + 1) * sizeof(WCHAR));
380 if (NewObject->Name == NULL)
381 {
382 if (MembersKeyHandle != NULL)
383 NtClose(MembersKeyHandle);
384 NtClose(ObjectKeyHandle);
385 RtlFreeHeap(RtlGetProcessHeap(), 0, NewObject);
386 return STATUS_NO_MEMORY;
387 }
388
389 wcscpy(NewObject->Name, ObjectName);
390 NewObject->Signature = SAMP_DB_SIGNATURE;
391 NewObject->RefCount = 1;
392 NewObject->ObjectType = ObjectType;
393 NewObject->Access = DesiredAccess;
394 NewObject->KeyHandle = ObjectKeyHandle;
395 NewObject->MembersKeyHandle = MembersKeyHandle;
396 NewObject->RelativeId = RelativeId;
397 NewObject->ParentObject = ParentObject;
398
399 if (ParentObject != NULL)
400 ParentObject->RefCount++;
401
402 *DbObject = NewObject;
403
404 return STATUS_SUCCESS;
405 }
406
407
408 NTSTATUS
409 SampValidateDbObject(SAMPR_HANDLE Handle,
410 SAM_DB_OBJECT_TYPE ObjectType,
411 ACCESS_MASK DesiredAccess,
412 PSAM_DB_OBJECT *DbObject)
413 {
414 PSAM_DB_OBJECT LocalObject = (PSAM_DB_OBJECT)Handle;
415 BOOLEAN bValid = FALSE;
416
417 _SEH2_TRY
418 {
419 if (LocalObject->Signature == SAMP_DB_SIGNATURE)
420 {
421 if ((ObjectType == SamDbIgnoreObject) ||
422 (LocalObject->ObjectType == ObjectType))
423 bValid = TRUE;
424 }
425 }
426 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
427 {
428 bValid = FALSE;
429 }
430 _SEH2_END;
431
432 if (bValid == FALSE)
433 return STATUS_INVALID_HANDLE;
434
435 if (DesiredAccess != 0)
436 {
437 /* Check for granted access rights */
438 if ((LocalObject->Access & DesiredAccess) != DesiredAccess)
439 {
440 ERR("SampValidateDbObject access check failed %08lx %08lx\n",
441 LocalObject->Access, DesiredAccess);
442 return STATUS_ACCESS_DENIED;
443 }
444 }
445
446 if (DbObject != NULL)
447 *DbObject = LocalObject;
448
449 return STATUS_SUCCESS;
450 }
451
452
453 NTSTATUS
454 SampCloseDbObject(PSAM_DB_OBJECT DbObject)
455 {
456 PSAM_DB_OBJECT ParentObject = NULL;
457 NTSTATUS Status = STATUS_SUCCESS;
458
459 DbObject->RefCount--;
460
461 if (DbObject->RefCount > 0)
462 return STATUS_SUCCESS;
463
464 if (DbObject->KeyHandle != NULL)
465 NtClose(DbObject->KeyHandle);
466
467 if (DbObject->MembersKeyHandle != NULL)
468 NtClose(DbObject->MembersKeyHandle);
469
470 if (DbObject->ParentObject != NULL)
471 ParentObject = DbObject->ParentObject;
472
473 if (DbObject->Name != NULL)
474 RtlFreeHeap(RtlGetProcessHeap(), 0, DbObject->Name);
475
476 RtlFreeHeap(RtlGetProcessHeap(), 0, DbObject);
477
478 if (ParentObject != NULL)
479 {
480 ParentObject->RefCount--;
481
482 if (ParentObject->RefCount == 0)
483 Status = SampCloseDbObject(ParentObject);
484 }
485
486 return Status;
487 }
488
489
490 NTSTATUS
491 SampSetAccountNameInDomain(IN PSAM_DB_OBJECT DomainObject,
492 IN LPCWSTR lpContainerName,
493 IN LPCWSTR lpAccountName,
494 IN ULONG ulRelativeId)
495 {
496 OBJECT_ATTRIBUTES ObjectAttributes;
497 UNICODE_STRING KeyName;
498 UNICODE_STRING ValueName;
499 HANDLE ContainerKeyHandle = NULL;
500 HANDLE NamesKeyHandle = NULL;
501 NTSTATUS Status;
502
503 TRACE("SampSetAccountNameInDomain()\n");
504
505 /* Open the container key */
506 RtlInitUnicodeString(&KeyName, lpContainerName);
507
508 InitializeObjectAttributes(&ObjectAttributes,
509 &KeyName,
510 OBJ_CASE_INSENSITIVE,
511 DomainObject->KeyHandle,
512 NULL);
513
514 Status = NtOpenKey(&ContainerKeyHandle,
515 KEY_ALL_ACCESS,
516 &ObjectAttributes);
517 if (!NT_SUCCESS(Status))
518 return Status;
519
520 /* Open the 'Names' key */
521 RtlInitUnicodeString(&KeyName, L"Names");
522
523 InitializeObjectAttributes(&ObjectAttributes,
524 &KeyName,
525 OBJ_CASE_INSENSITIVE,
526 ContainerKeyHandle,
527 NULL);
528
529 Status = NtOpenKey(&NamesKeyHandle,
530 KEY_ALL_ACCESS,
531 &ObjectAttributes);
532 if (!NT_SUCCESS(Status))
533 goto done;
534
535 /* Set the alias value */
536 RtlInitUnicodeString(&ValueName, lpAccountName);
537
538 Status = NtSetValueKey(NamesKeyHandle,
539 &ValueName,
540 0,
541 REG_DWORD,
542 (LPVOID)&ulRelativeId,
543 sizeof(ULONG));
544
545 done:
546 if (NamesKeyHandle)
547 NtClose(NamesKeyHandle);
548
549 if (ContainerKeyHandle)
550 NtClose(ContainerKeyHandle);
551
552 return Status;
553 }
554
555
556 NTSTATUS
557 SampCheckAccountNameInDomain(IN PSAM_DB_OBJECT DomainObject,
558 IN LPWSTR lpAccountName)
559 {
560 HANDLE AccountKey;
561 HANDLE NamesKey;
562 NTSTATUS Status;
563
564 TRACE("SampCheckAccountNameInDomain()\n");
565
566 Status = SampRegOpenKey(DomainObject->KeyHandle,
567 L"Aliases",
568 KEY_READ,
569 &AccountKey);
570 if (NT_SUCCESS(Status))
571 {
572 Status = SampRegOpenKey(AccountKey,
573 L"Names",
574 KEY_READ,
575 &NamesKey);
576 if (NT_SUCCESS(Status))
577 {
578 Status = SampRegQueryValue(NamesKey,
579 lpAccountName,
580 NULL,
581 NULL,
582 NULL);
583 if (Status == STATUS_SUCCESS)
584 {
585 SampRegCloseKey(NamesKey);
586 Status = STATUS_ALIAS_EXISTS;
587 }
588 else if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
589 Status = STATUS_SUCCESS;
590 }
591
592 SampRegCloseKey(AccountKey);
593 }
594
595 if (!NT_SUCCESS(Status))
596 {
597 TRACE("Checking for alias account failed (Status 0x%08lx)\n", Status);
598 return Status;
599 }
600
601 Status = SampRegOpenKey(DomainObject->KeyHandle,
602 L"Groups",
603 KEY_READ,
604 &AccountKey);
605 if (NT_SUCCESS(Status))
606 {
607 Status = SampRegOpenKey(AccountKey,
608 L"Names",
609 KEY_READ,
610 &NamesKey);
611 if (NT_SUCCESS(Status))
612 {
613 Status = SampRegQueryValue(NamesKey,
614 lpAccountName,
615 NULL,
616 NULL,
617 NULL);
618 if (Status == STATUS_SUCCESS)
619 {
620 SampRegCloseKey(NamesKey);
621 Status = STATUS_ALIAS_EXISTS;
622 }
623 else if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
624 Status = STATUS_SUCCESS;
625 }
626
627 SampRegCloseKey(AccountKey);
628 }
629
630 if (!NT_SUCCESS(Status))
631 {
632 TRACE("Checking for group account failed (Status 0x%08lx)\n", Status);
633 return Status;
634 }
635
636 Status = SampRegOpenKey(DomainObject->KeyHandle,
637 L"Users",
638 KEY_READ,
639 &AccountKey);
640 if (NT_SUCCESS(Status))
641 {
642 Status = SampRegOpenKey(AccountKey,
643 L"Names",
644 KEY_READ,
645 &NamesKey);
646 if (NT_SUCCESS(Status))
647 {
648 Status = SampRegQueryValue(NamesKey,
649 lpAccountName,
650 NULL,
651 NULL,
652 NULL);
653 if (Status == STATUS_SUCCESS)
654 {
655 SampRegCloseKey(NamesKey);
656 Status = STATUS_ALIAS_EXISTS;
657 }
658 else if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
659 Status = STATUS_SUCCESS;
660 }
661
662 SampRegCloseKey(AccountKey);
663 }
664
665 if (!NT_SUCCESS(Status))
666 {
667 TRACE("Checking for user account failed (Status 0x%08lx)\n", Status);
668 }
669
670 return Status;
671 }
672
673
674 NTSTATUS
675 SampSetObjectAttribute(PSAM_DB_OBJECT DbObject,
676 LPWSTR AttributeName,
677 ULONG AttributeType,
678 LPVOID AttributeData,
679 ULONG AttributeSize)
680 {
681 UNICODE_STRING ValueName;
682
683 RtlInitUnicodeString(&ValueName,
684 AttributeName);
685
686 return ZwSetValueKey(DbObject->KeyHandle,
687 &ValueName,
688 0,
689 AttributeType,
690 AttributeData,
691 AttributeSize);
692 }
693
694
695 NTSTATUS
696 SampGetObjectAttribute(PSAM_DB_OBJECT DbObject,
697 LPWSTR AttributeName,
698 PULONG AttributeType,
699 LPVOID AttributeData,
700 PULONG AttributeSize)
701 {
702 PKEY_VALUE_PARTIAL_INFORMATION ValueInfo;
703 UNICODE_STRING ValueName;
704 ULONG BufferLength = 0;
705 NTSTATUS Status;
706
707 RtlInitUnicodeString(&ValueName,
708 AttributeName);
709
710 if (AttributeSize != NULL)
711 BufferLength = *AttributeSize;
712
713 BufferLength += FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data);
714
715 /* Allocate memory for the value */
716 ValueInfo = RtlAllocateHeap(RtlGetProcessHeap(), 0, BufferLength);
717 if (ValueInfo == NULL)
718 return STATUS_NO_MEMORY;
719
720 /* Query the value */
721 Status = ZwQueryValueKey(DbObject->KeyHandle,
722 &ValueName,
723 KeyValuePartialInformation,
724 ValueInfo,
725 BufferLength,
726 &BufferLength);
727 if ((NT_SUCCESS(Status)) || (Status == STATUS_BUFFER_OVERFLOW))
728 {
729 if (AttributeType != NULL)
730 *AttributeType = ValueInfo->Type;
731
732 if (AttributeSize != NULL)
733 *AttributeSize = ValueInfo->DataLength;
734 }
735
736 /* Check if the caller wanted data back, and we got it */
737 if ((NT_SUCCESS(Status)) && (AttributeData != NULL))
738 {
739 /* Copy it */
740 RtlMoveMemory(AttributeData,
741 ValueInfo->Data,
742 ValueInfo->DataLength);
743 }
744
745 /* Free the memory and return status */
746 RtlFreeHeap(RtlGetProcessHeap(), 0, ValueInfo);
747
748 return Status;
749 }
750
751
752 NTSTATUS
753 SampGetObjectAttributeString(PSAM_DB_OBJECT DbObject,
754 LPWSTR AttributeName,
755 RPC_UNICODE_STRING *String)
756 {
757 ULONG Length = 0;
758 NTSTATUS Status;
759
760 Status = SampGetObjectAttribute(DbObject,
761 AttributeName,
762 NULL,
763 NULL,
764 &Length);
765 if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)
766 {
767 TRACE("Status 0x%08lx\n", Status);
768 goto done;
769 }
770
771 String->Length = (USHORT)(Length - sizeof(WCHAR));
772 String->MaximumLength = (USHORT)Length;
773 String->Buffer = midl_user_allocate(Length);
774 if (String->Buffer == NULL)
775 {
776 Status = STATUS_INSUFFICIENT_RESOURCES;
777 goto done;
778 }
779
780 TRACE("Length: %lu\n", Length);
781 Status = SampGetObjectAttribute(DbObject,
782 AttributeName,
783 NULL,
784 (PVOID)String->Buffer,
785 &Length);
786 if (!NT_SUCCESS(Status))
787 {
788 TRACE("Status 0x%08lx\n", Status);
789 goto done;
790 }
791
792 done:
793 if (!NT_SUCCESS(Status))
794 {
795 if (String->Buffer != NULL)
796 {
797 midl_user_free(String->Buffer);
798 String->Buffer = NULL;
799 }
800 }
801
802 return Status;
803 }
804
805 /* EOF */
806