xref: /reactos/dll/win32/samsrv/user.c (revision c2c66aff)
1 /*
2  * PROJECT:     Local Security Authority Server DLL
3  * LICENSE:     GPL - See COPYING in the top level directory
4  * FILE:        dll/win32/samsrv/user.c
5  * PURPOSE:     User specific helper functions
6  * COPYRIGHT:   Copyright 2013 Eric Kohl
7  */
8 
9 #include "samsrv.h"
10 
11 /* FUNCTIONS ***************************************************************/
12 
13 NTSTATUS
SampOpenUserObject(IN PSAM_DB_OBJECT DomainObject,IN ULONG UserId,IN ACCESS_MASK DesiredAccess,OUT PSAM_DB_OBJECT * UserObject)14 SampOpenUserObject(IN PSAM_DB_OBJECT DomainObject,
15                    IN ULONG UserId,
16                    IN ACCESS_MASK DesiredAccess,
17                    OUT PSAM_DB_OBJECT *UserObject)
18 {
19     WCHAR szRid[9];
20 
21     TRACE("(%p %lu %lx %p)\n",
22           DomainObject, UserId, DesiredAccess, UserObject);
23 
24     /* Convert the RID into a string (hex) */
25     swprintf(szRid, L"%08lX", UserId);
26 
27     /* Create the user object */
28     return SampOpenDbObject(DomainObject,
29                             L"Users",
30                             szRid,
31                             UserId,
32                             SamDbUserObject,
33                             DesiredAccess,
34                             UserObject);
35 }
36 
37 
38 NTSTATUS
SampAddGroupMembershipToUser(IN PSAM_DB_OBJECT UserObject,IN ULONG GroupId,IN ULONG Attributes)39 SampAddGroupMembershipToUser(IN PSAM_DB_OBJECT UserObject,
40                              IN ULONG GroupId,
41                              IN ULONG Attributes)
42 {
43     PGROUP_MEMBERSHIP GroupsBuffer = NULL;
44     ULONG GroupsCount = 0;
45     ULONG Length = 0;
46     ULONG i;
47     NTSTATUS Status;
48 
49     TRACE("(%p %lu %lx)\n",
50           UserObject, GroupId, Attributes);
51 
52     Status = SampGetObjectAttribute(UserObject,
53                                     L"Groups",
54                                     NULL,
55                                     NULL,
56                                     &Length);
57     if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_NOT_FOUND)
58         goto done;
59 
60     GroupsBuffer = midl_user_allocate(Length + sizeof(GROUP_MEMBERSHIP));
61     if (GroupsBuffer == NULL)
62     {
63         Status = STATUS_INSUFFICIENT_RESOURCES;
64         goto done;
65     }
66 
67     if (Status != STATUS_OBJECT_NAME_NOT_FOUND)
68     {
69         Status = SampGetObjectAttribute(UserObject,
70                                         L"Groups",
71                                         NULL,
72                                         GroupsBuffer,
73                                         &Length);
74         if (!NT_SUCCESS(Status))
75             goto done;
76 
77         GroupsCount = Length / sizeof(GROUP_MEMBERSHIP);
78     }
79 
80     for (i = 0; i < GroupsCount; i++)
81     {
82         if (GroupsBuffer[i].RelativeId == GroupId)
83         {
84             Status = STATUS_MEMBER_IN_GROUP;
85             goto done;
86         }
87     }
88 
89     GroupsBuffer[GroupsCount].RelativeId = GroupId;
90     GroupsBuffer[GroupsCount].Attributes = Attributes;
91     Length += sizeof(GROUP_MEMBERSHIP);
92 
93     Status = SampSetObjectAttribute(UserObject,
94                                     L"Groups",
95                                     REG_BINARY,
96                                     GroupsBuffer,
97                                     Length);
98 
99 done:
100     if (GroupsBuffer != NULL)
101         midl_user_free(GroupsBuffer);
102 
103     return Status;
104 }
105 
106 
107 NTSTATUS
SampRemoveGroupMembershipFromUser(IN PSAM_DB_OBJECT UserObject,IN ULONG GroupId)108 SampRemoveGroupMembershipFromUser(IN PSAM_DB_OBJECT UserObject,
109                                   IN ULONG GroupId)
110 {
111     PGROUP_MEMBERSHIP GroupsBuffer = NULL;
112     ULONG GroupsCount = 0;
113     ULONG Length = 0;
114     ULONG i;
115     NTSTATUS Status = STATUS_SUCCESS;
116 
117     TRACE("(%p %lu)\n",
118           UserObject, GroupId);
119 
120     SampGetObjectAttribute(UserObject,
121                            L"Groups",
122                            NULL,
123                            NULL,
124                            &Length);
125 
126     if (Length == 0)
127         return STATUS_MEMBER_NOT_IN_GROUP;
128 
129     GroupsBuffer = midl_user_allocate(Length);
130     if (GroupsBuffer == NULL)
131     {
132         Status = STATUS_INSUFFICIENT_RESOURCES;
133         goto done;
134     }
135 
136     Status = SampGetObjectAttribute(UserObject,
137                                     L"Groups",
138                                     NULL,
139                                     GroupsBuffer,
140                                     &Length);
141     if (!NT_SUCCESS(Status))
142         goto done;
143 
144     Status = STATUS_MEMBER_NOT_IN_GROUP;
145 
146     GroupsCount = Length / sizeof(GROUP_MEMBERSHIP);
147     for (i = 0; i < GroupsCount; i++)
148     {
149         if (GroupsBuffer[i].RelativeId == GroupId)
150         {
151             Length -= sizeof(GROUP_MEMBERSHIP);
152             Status = STATUS_SUCCESS;
153 
154             if (GroupsCount - i - 1 > 0)
155             {
156                 CopyMemory(&GroupsBuffer[i],
157                            &GroupsBuffer[i + 1],
158                            (GroupsCount - i - 1) * sizeof(GROUP_MEMBERSHIP));
159             }
160 
161             break;
162         }
163     }
164 
165     if (!NT_SUCCESS(Status))
166         goto done;
167 
168     Status = SampSetObjectAttribute(UserObject,
169                                     L"Groups",
170                                     REG_BINARY,
171                                     GroupsBuffer,
172                                     Length);
173 
174 done:
175     if (GroupsBuffer != NULL)
176         midl_user_free(GroupsBuffer);
177 
178     return Status;
179 }
180 
181 
182 NTSTATUS
SampGetUserGroupAttributes(IN PSAM_DB_OBJECT DomainObject,IN ULONG UserId,IN ULONG GroupId,OUT PULONG GroupAttributes)183 SampGetUserGroupAttributes(IN PSAM_DB_OBJECT DomainObject,
184                            IN ULONG UserId,
185                            IN ULONG GroupId,
186                            OUT PULONG GroupAttributes)
187 {
188     PSAM_DB_OBJECT UserObject = NULL;
189     PGROUP_MEMBERSHIP GroupsBuffer = NULL;
190     ULONG Length = 0;
191     ULONG i;
192     NTSTATUS Status;
193 
194     Status = SampOpenUserObject(DomainObject,
195                                 UserId,
196                                 0,
197                                 &UserObject);
198     if (!NT_SUCCESS(Status))
199     {
200         return Status;
201     }
202 
203     SampGetObjectAttribute(UserObject,
204                            L"Groups",
205                            NULL,
206                            NULL,
207                            &Length);
208 
209     if (Length == 0)
210         return STATUS_UNSUCCESSFUL; /* FIXME */
211 
212     GroupsBuffer = midl_user_allocate(Length);
213     if (GroupsBuffer == NULL)
214     {
215         Status = STATUS_INSUFFICIENT_RESOURCES;
216         goto done;
217     }
218 
219     Status = SampGetObjectAttribute(UserObject,
220                                     L"Groups",
221                                     NULL,
222                                     GroupsBuffer,
223                                     &Length);
224     if (!NT_SUCCESS(Status))
225         goto done;
226 
227     for (i = 0; i < (Length / sizeof(GROUP_MEMBERSHIP)); i++)
228     {
229         if (GroupsBuffer[i].RelativeId == GroupId)
230         {
231             *GroupAttributes = GroupsBuffer[i].Attributes;
232             goto done;
233         }
234     }
235 
236 done:
237     if (GroupsBuffer != NULL)
238         midl_user_free(GroupsBuffer);
239 
240     if (UserObject != NULL)
241         SampCloseDbObject(UserObject);
242 
243     return Status;
244 }
245 
246 
247 NTSTATUS
SampSetUserGroupAttributes(IN PSAM_DB_OBJECT DomainObject,IN ULONG UserId,IN ULONG GroupId,IN ULONG GroupAttributes)248 SampSetUserGroupAttributes(IN PSAM_DB_OBJECT DomainObject,
249                            IN ULONG UserId,
250                            IN ULONG GroupId,
251                            IN ULONG GroupAttributes)
252 {
253     PSAM_DB_OBJECT UserObject = NULL;
254     PGROUP_MEMBERSHIP GroupsBuffer = NULL;
255     ULONG Length = 0;
256     ULONG i;
257     NTSTATUS Status;
258 
259     Status = SampOpenUserObject(DomainObject,
260                                 UserId,
261                                 0,
262                                 &UserObject);
263     if (!NT_SUCCESS(Status))
264     {
265         return Status;
266     }
267 
268     SampGetObjectAttribute(UserObject,
269                            L"Groups",
270                            NULL,
271                            NULL,
272                            &Length);
273 
274     if (Length == 0)
275         return STATUS_UNSUCCESSFUL; /* FIXME */
276 
277     GroupsBuffer = midl_user_allocate(Length);
278     if (GroupsBuffer == NULL)
279     {
280         Status = STATUS_INSUFFICIENT_RESOURCES;
281         goto done;
282     }
283 
284     Status = SampGetObjectAttribute(UserObject,
285                                     L"Groups",
286                                     NULL,
287                                     GroupsBuffer,
288                                     &Length);
289     if (!NT_SUCCESS(Status))
290         goto done;
291 
292     for (i = 0; i < (Length / sizeof(GROUP_MEMBERSHIP)); i++)
293     {
294         if (GroupsBuffer[i].RelativeId == GroupId)
295         {
296             GroupsBuffer[i].Attributes = GroupAttributes;
297             break;
298         }
299     }
300 
301     Status = SampSetObjectAttribute(UserObject,
302                                     L"Groups",
303                                     REG_BINARY,
304                                     GroupsBuffer,
305                                     Length);
306 
307 done:
308     if (GroupsBuffer != NULL)
309         midl_user_free(GroupsBuffer);
310 
311     if (UserObject != NULL)
312         SampCloseDbObject(UserObject);
313 
314     return Status;
315 }
316 
317 
318 NTSTATUS
SampRemoveUserFromAllGroups(IN PSAM_DB_OBJECT UserObject)319 SampRemoveUserFromAllGroups(IN PSAM_DB_OBJECT UserObject)
320 {
321     PGROUP_MEMBERSHIP GroupsBuffer = NULL;
322     PSAM_DB_OBJECT GroupObject;
323     ULONG Length = 0;
324     ULONG i;
325     NTSTATUS Status;
326 
327     SampGetObjectAttribute(UserObject,
328                            L"Groups",
329                            NULL,
330                            NULL,
331                            &Length);
332 
333     if (Length == 0)
334         return STATUS_SUCCESS;
335 
336     GroupsBuffer = midl_user_allocate(Length);
337     if (GroupsBuffer == NULL)
338     {
339         Status = STATUS_INSUFFICIENT_RESOURCES;
340         goto done;
341     }
342 
343     Status = SampGetObjectAttribute(UserObject,
344                                     L"Groups",
345                                     NULL,
346                                     GroupsBuffer,
347                                     &Length);
348     if (!NT_SUCCESS(Status))
349         goto done;
350 
351     for (i = 0; i < (Length / sizeof(GROUP_MEMBERSHIP)); i++)
352     {
353         Status = SampOpenGroupObject(UserObject->ParentObject,
354                                      GroupsBuffer[i].RelativeId,
355                                      0,
356                                      &GroupObject);
357         if (!NT_SUCCESS(Status))
358         {
359             goto done;
360         }
361 
362         Status = SampRemoveMemberFromGroup(GroupObject,
363                                            UserObject->RelativeId);
364         if (Status == STATUS_MEMBER_NOT_IN_GROUP)
365             Status = STATUS_SUCCESS;
366 
367         SampCloseDbObject(GroupObject);
368 
369         if (!NT_SUCCESS(Status))
370         {
371             goto done;
372         }
373     }
374 
375     /* Remove all groups from the Groups attribute */
376     Status = SampSetObjectAttribute(UserObject,
377                                     L"Groups",
378                                     REG_BINARY,
379                                     NULL,
380                                     0);
381 
382 done:
383     if (GroupsBuffer != NULL)
384         midl_user_free(GroupsBuffer);
385 
386     return Status;
387 }
388 
389 
390 NTSTATUS
SampRemoveUserFromAllAliases(IN PSAM_DB_OBJECT UserObject)391 SampRemoveUserFromAllAliases(IN PSAM_DB_OBJECT UserObject)
392 {
393     FIXME("(%p)\n", UserObject);
394     return STATUS_SUCCESS;
395 }
396 
397 
398 NTSTATUS
SampSetUserPassword(IN PSAM_DB_OBJECT UserObject,IN PENCRYPTED_NT_OWF_PASSWORD NtPassword,IN BOOLEAN NtPasswordPresent,IN PENCRYPTED_LM_OWF_PASSWORD LmPassword,IN BOOLEAN LmPasswordPresent)399 SampSetUserPassword(IN PSAM_DB_OBJECT UserObject,
400                     IN PENCRYPTED_NT_OWF_PASSWORD NtPassword,
401                     IN BOOLEAN NtPasswordPresent,
402                     IN PENCRYPTED_LM_OWF_PASSWORD LmPassword,
403                     IN BOOLEAN LmPasswordPresent)
404 {
405     PENCRYPTED_NT_OWF_PASSWORD NtHistory = NULL;
406     PENCRYPTED_LM_OWF_PASSWORD LmHistory = NULL;
407     ULONG NtHistoryLength = 0;
408     ULONG LmHistoryLength = 0;
409     ULONG CurrentHistoryLength;
410     ULONG MaxHistoryLength = 3;
411     ULONG Length = 0;
412     BOOLEAN UseNtPassword;
413     BOOLEAN UseLmPassword;
414     NTSTATUS Status;
415 
416     UseNtPassword =
417        ((NtPasswordPresent != FALSE) &&
418         (NtPassword != NULL) &&
419         (memcmp(NtPassword, &EmptyNtHash, sizeof(ENCRYPTED_NT_OWF_PASSWORD)) != 0));
420 
421     UseLmPassword =
422        ((LmPasswordPresent != FALSE) &&
423         (LmPassword != NULL) &&
424         (memcmp(LmPassword, &EmptyLmHash, sizeof(ENCRYPTED_LM_OWF_PASSWORD)) != 0));
425 
426     /* Update the NT password history only if we have a new non-empty NT password */
427     if (UseNtPassword)
428     {
429         /* Get the size of the NT history */
430         SampGetObjectAttribute(UserObject,
431                                L"NTPwdHistory",
432                                NULL,
433                                NULL,
434                                &Length);
435 
436         CurrentHistoryLength = Length / sizeof(ENCRYPTED_NT_OWF_PASSWORD);
437         if (CurrentHistoryLength < MaxHistoryLength)
438         {
439             NtHistoryLength = (CurrentHistoryLength + 1) * sizeof(ENCRYPTED_NT_OWF_PASSWORD);
440         }
441         else
442         {
443             NtHistoryLength = MaxHistoryLength * sizeof(ENCRYPTED_NT_OWF_PASSWORD);
444         }
445 
446         /* Allocate the history buffer */
447         NtHistory = midl_user_allocate(NtHistoryLength);
448         if (NtHistory == NULL)
449             return STATUS_INSUFFICIENT_RESOURCES;
450 
451         if (Length > 0)
452         {
453             /* Get the history */
454             Status = SampGetObjectAttribute(UserObject,
455                                             L"NTPwdHistory",
456                                             NULL,
457                                             NtHistory,
458                                             &Length);
459             if (!NT_SUCCESS(Status))
460                 goto done;
461         }
462 
463         /* Move the old passwords down by one entry */
464         if (NtHistoryLength > sizeof(ENCRYPTED_NT_OWF_PASSWORD))
465         {
466             MoveMemory(&(NtHistory[1]),
467                        &(NtHistory[0]),
468                        NtHistoryLength - sizeof(ENCRYPTED_NT_OWF_PASSWORD));
469         }
470 
471         /* Add the new password to the top of the history */
472         if (NtPasswordPresent)
473         {
474             CopyMemory(&(NtHistory[0]),
475                        NtPassword,
476                        sizeof(ENCRYPTED_NT_OWF_PASSWORD));
477         }
478         else
479         {
480             ZeroMemory(&(NtHistory[0]),
481                        sizeof(ENCRYPTED_NT_OWF_PASSWORD));
482         }
483 
484         /* Set the history */
485         Status = SampSetObjectAttribute(UserObject,
486                                         L"NTPwdHistory",
487                                         REG_BINARY,
488                                         (PVOID)NtHistory,
489                                         NtHistoryLength);
490         if (!NT_SUCCESS(Status))
491             goto done;
492     }
493 
494     /* Update the LM password history only if we have a new non-empty LM password */
495     if (UseLmPassword)
496     {
497         /* Get the size of the LM history */
498         Length = 0;
499         SampGetObjectAttribute(UserObject,
500                                L"LMPwdHistory",
501                                NULL,
502                                NULL,
503                                &Length);
504 
505         CurrentHistoryLength = Length / sizeof(ENCRYPTED_LM_OWF_PASSWORD);
506         if (CurrentHistoryLength < MaxHistoryLength)
507         {
508             LmHistoryLength = (CurrentHistoryLength + 1) * sizeof(ENCRYPTED_LM_OWF_PASSWORD);
509         }
510         else
511         {
512             LmHistoryLength = MaxHistoryLength * sizeof(ENCRYPTED_LM_OWF_PASSWORD);
513         }
514 
515         /* Allocate the history buffer */
516         LmHistory = midl_user_allocate(LmHistoryLength);
517         if (LmHistory == NULL)
518             return STATUS_INSUFFICIENT_RESOURCES;
519 
520         if (Length > 0)
521         {
522             /* Get the history */
523             Status = SampGetObjectAttribute(UserObject,
524                                             L"LMPwdHistory",
525                                             NULL,
526                                             LmHistory,
527                                             &Length);
528             if (!NT_SUCCESS(Status))
529                 goto done;
530         }
531 
532         /* Move the old passwords down by one entry */
533         if (LmHistoryLength > sizeof(ENCRYPTED_LM_OWF_PASSWORD))
534         {
535             MoveMemory(&(LmHistory[1]),
536                        &(LmHistory[0]),
537                        LmHistoryLength - sizeof(ENCRYPTED_LM_OWF_PASSWORD));
538         }
539 
540         /* Add the new password to the top of the history */
541         if (LmPasswordPresent)
542         {
543             CopyMemory(&(LmHistory[0]),
544                        LmPassword,
545                        sizeof(ENCRYPTED_LM_OWF_PASSWORD));
546         }
547         else
548         {
549             ZeroMemory(&(LmHistory[0]),
550                        sizeof(ENCRYPTED_LM_OWF_PASSWORD));
551         }
552 
553         /* Set the LM password history */
554         Status = SampSetObjectAttribute(UserObject,
555                                         L"LMPwdHistory",
556                                         REG_BINARY,
557                                         (PVOID)LmHistory,
558                                         LmHistoryLength);
559         if (!NT_SUCCESS(Status))
560             goto done;
561     }
562 
563     /* Set the new NT password */
564     if (UseNtPassword)
565     {
566         Status = SampSetObjectAttribute(UserObject,
567                                         L"NTPwd",
568                                         REG_BINARY,
569                                         (PVOID)NtPassword,
570                                         sizeof(ENCRYPTED_NT_OWF_PASSWORD));
571         if (!NT_SUCCESS(Status))
572             goto done;
573     }
574     else
575     {
576         Status = SampSetObjectAttribute(UserObject,
577                                         L"NTPwd",
578                                         REG_BINARY,
579                                         &EmptyNtHash,
580                                         sizeof(ENCRYPTED_NT_OWF_PASSWORD));
581         if (!NT_SUCCESS(Status))
582             goto done;
583     }
584 
585     /* Set the new LM password */
586     if (UseLmPassword)
587     {
588         Status = SampSetObjectAttribute(UserObject,
589                                         L"LMPwd",
590                                         REG_BINARY,
591                                         (PVOID)LmPassword,
592                                         sizeof(ENCRYPTED_LM_OWF_PASSWORD));
593         if (!NT_SUCCESS(Status))
594             goto done;
595     }
596     else
597     {
598         Status = SampSetObjectAttribute(UserObject,
599                                         L"LMPwd",
600                                         REG_BINARY,
601                                         &EmptyLmHash,
602                                         sizeof(ENCRYPTED_LM_OWF_PASSWORD));
603         if (!NT_SUCCESS(Status))
604             goto done;
605     }
606 
607 done:
608     if (NtHistory != NULL)
609         midl_user_free(NtHistory);
610 
611     if (LmHistory != NULL)
612         midl_user_free(LmHistory);
613 
614     return Status;
615 }
616 
617 
618 NTSTATUS
SampGetLogonHoursAttribute(IN PSAM_DB_OBJECT UserObject,IN OUT PSAMPR_LOGON_HOURS LogonHours)619 SampGetLogonHoursAttribute(IN PSAM_DB_OBJECT UserObject,
620                           IN OUT PSAMPR_LOGON_HOURS LogonHours)
621 {
622     PUCHAR RawBuffer = NULL;
623     ULONG Length = 0;
624     ULONG BufferLength = 0;
625     NTSTATUS Status;
626 
627     Status = SampGetObjectAttribute(UserObject,
628                                     L"LogonHours",
629                                     NULL,
630                                     NULL,
631                                     &Length);
632     if (Status != STATUS_BUFFER_OVERFLOW)
633     {
634         TRACE("SampGetObjectAttribute failed (Status 0x%08lx)\n", Status);
635         return Status;
636     }
637 
638     Status = STATUS_SUCCESS;
639 
640     if (Length == 0)
641     {
642         LogonHours->UnitsPerWeek = 0;
643         LogonHours->LogonHours = NULL;
644     }
645     else
646     {
647         RawBuffer = midl_user_allocate(Length);
648         if (RawBuffer == NULL)
649         {
650             Status = STATUS_INSUFFICIENT_RESOURCES;
651             goto done;
652         }
653 
654         Status = SampGetObjectAttribute(UserObject,
655                                         L"LogonHours",
656                                         NULL,
657                                         (PVOID)RawBuffer,
658                                         &Length);
659         if (!NT_SUCCESS(Status))
660             goto done;
661 
662         LogonHours->UnitsPerWeek = *((PUSHORT)RawBuffer);
663 
664         BufferLength = (((ULONG)LogonHours->UnitsPerWeek) + 7) / 8;
665 
666         LogonHours->LogonHours = midl_user_allocate(BufferLength);
667         if (LogonHours->LogonHours == NULL)
668         {
669             TRACE("Failed to allocate LogonHours buffer!\n");
670             Status = STATUS_INSUFFICIENT_RESOURCES;
671             goto done;
672         }
673 
674         memcpy(LogonHours->LogonHours,
675                &(RawBuffer[2]),
676                BufferLength);
677     }
678 
679 done:
680 
681     if (RawBuffer != NULL)
682         midl_user_free(RawBuffer);
683 
684     return Status;
685 }
686 
687 
688 NTSTATUS
SampSetLogonHoursAttribute(IN PSAM_DB_OBJECT UserObject,IN PSAMPR_LOGON_HOURS LogonHours)689 SampSetLogonHoursAttribute(IN PSAM_DB_OBJECT UserObject,
690                           IN PSAMPR_LOGON_HOURS LogonHours)
691 {
692     PUCHAR RawBuffer = NULL;
693     ULONG BufferLength;
694     ULONG Length = 0;
695     NTSTATUS Status;
696 
697     if (LogonHours->UnitsPerWeek > 0)
698     {
699         BufferLength = (((ULONG)LogonHours->UnitsPerWeek) + 7) / 8;
700 
701         Length = BufferLength + sizeof(USHORT);
702 
703         RawBuffer = midl_user_allocate(Length);
704         if (RawBuffer == NULL)
705         {
706             Status = STATUS_INSUFFICIENT_RESOURCES;
707             goto done;
708         }
709 
710         *((PUSHORT)RawBuffer) = LogonHours->UnitsPerWeek;
711 
712         memcpy(&(RawBuffer[2]),
713                LogonHours->LogonHours,
714                BufferLength);
715     }
716 
717     Status = SampSetObjectAttribute(UserObject,
718                                     L"LogonHours",
719                                     REG_BINARY,
720                                     RawBuffer,
721                                     Length);
722 
723 done:
724     if (RawBuffer != NULL)
725         midl_user_free(RawBuffer);
726 
727     return Status;
728 }
729 
730 /* EOF */
731