enumerate the permission names and display them
[reactos.git] / reactos / lib / aclui / aclui.c
1 /*
2 * ReactOS Access Control List Editor
3 * Copyright (C) 2004 ReactOS Team
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19 /* $Id$
20 *
21 * PROJECT: ReactOS Access Control List Editor
22 * FILE: lib/aclui/aclui.c
23 * PURPOSE: Access Control List Editor
24 * PROGRAMMER: Thomas Weidenmueller <w3seek@reactos.com>
25 *
26 * UPDATE HISTORY:
27 * 08/10/2004 Created
28 */
29 #include "acluilib.h"
30
31 HINSTANCE hDllInstance;
32
33 static VOID
34 DestroySecurityPage(IN PSECURITY_PAGE sp)
35 {
36 if(sp->hiUsrs != NULL)
37 {
38 ImageList_Destroy(sp->hiUsrs);
39 }
40
41 HeapFree(GetProcessHeap(),
42 0,
43 sp);
44 }
45
46 static VOID
47 FreeAceList(IN PACE_LISTITEM *AceListHead)
48 {
49 PACE_LISTITEM CurItem, NextItem;
50
51 CurItem = *AceListHead;
52 while (CurItem != NULL)
53 {
54 /* free the SID string if present */
55 if (CurItem->DisplayString != NULL)
56 {
57 LocalFree((HLOCAL)CurItem->DisplayString);
58 }
59
60 /* free the ACE list item */
61 NextItem = CurItem->Next;
62 HeapFree(GetProcessHeap(),
63 0,
64 CurItem);
65 CurItem = NextItem;
66 }
67
68 *AceListHead = NULL;
69 }
70
71 static PACE_LISTITEM
72 FindSidInAceList(IN PACE_LISTITEM AceListHead,
73 IN PSID Sid)
74 {
75 PACE_LISTITEM CurItem;
76
77 for (CurItem = AceListHead;
78 CurItem != NULL;
79 CurItem = CurItem->Next)
80 {
81 if (EqualSid((PSID)(CurItem + 1),
82 Sid))
83 {
84 return CurItem;
85 }
86 }
87
88 return NULL;
89 }
90
91 static VOID
92 ReloadUsersGroupsList(IN PSECURITY_PAGE sp)
93 {
94 PSECURITY_DESCRIPTOR SecurityDescriptor;
95 BOOL DaclPresent, DaclDefaulted;
96 PACL Dacl = NULL;
97 HRESULT hRet;
98
99 /* delete the cached ACL */
100 FreeAceList(&sp->AceListHead);
101
102 /* query the ACL */
103 hRet = sp->psi->lpVtbl->GetSecurity(sp->psi,
104 DACL_SECURITY_INFORMATION,
105 &SecurityDescriptor,
106 FALSE);
107 if (SUCCEEDED(hRet) && SecurityDescriptor != NULL)
108 {
109 if (GetSecurityDescriptorDacl(SecurityDescriptor,
110 &DaclPresent,
111 &Dacl,
112 &DaclDefaulted))
113 {
114 PACE_LISTITEM AceListItem, *NextAcePtr;
115 PSID Sid;
116 PVOID Ace;
117 ULONG AceIndex;
118 DWORD AccountNameSize, DomainNameSize, SidLength;
119 SID_NAME_USE SidNameUse;
120 DWORD LookupResult;
121
122 NextAcePtr = &sp->AceListHead;
123
124 for (AceIndex = 0;
125 AceIndex < Dacl->AceCount;
126 AceIndex++)
127 {
128 GetAce(Dacl,
129 AceIndex,
130 &Ace);
131
132 Sid = (PSID)&((PACCESS_ALLOWED_ACE)Ace)->SidStart;
133
134 if (!FindSidInAceList(sp->AceListHead,
135 Sid))
136 {
137 SidLength = GetLengthSid(Sid);
138
139 AccountNameSize = 0;
140 DomainNameSize = 0;
141
142 /* calculate the size of the buffer we need to calculate */
143 LookupAccountSid(NULL, /* FIXME */
144 Sid,
145 NULL,
146 &AccountNameSize,
147 NULL,
148 &DomainNameSize,
149 &SidNameUse);
150
151 /* allocate the ace */
152 AceListItem = HeapAlloc(GetProcessHeap(),
153 0,
154 sizeof(ACE_LISTITEM) +
155 SidLength +
156 ((AccountNameSize + DomainNameSize) * sizeof(WCHAR)));
157 if (AceListItem != NULL)
158 {
159 AceListItem->AccountName = (LPWSTR)((ULONG_PTR)(AceListItem + 1) + SidLength);
160 AceListItem->DomainName = AceListItem->AccountName + AccountNameSize;
161
162 CopySid(SidLength,
163 (PSID)(AceListItem + 1),
164 Sid);
165
166 LookupResult = ERROR_SUCCESS;
167 if (!LookupAccountSid(NULL, /* FIXME */
168 Sid,
169 AceListItem->AccountName,
170 &AccountNameSize,
171 AceListItem->DomainName,
172 &DomainNameSize,
173 &SidNameUse))
174 {
175 LookupResult = GetLastError();
176 if (LookupResult != ERROR_NONE_MAPPED)
177 {
178 HeapFree(GetProcessHeap(),
179 0,
180 AceListItem);
181 continue;
182 }
183 }
184
185 if (AccountNameSize == 0)
186 {
187 AceListItem->AccountName = NULL;
188 }
189 if (DomainNameSize == 0)
190 {
191 AceListItem->DomainName = NULL;
192 }
193
194 AceListItem->Next = NULL;
195 if (LookupResult == ERROR_NONE_MAPPED)
196 {
197 if (!ConvertSidToStringSid(Sid,
198 &AceListItem->DisplayString))
199 {
200 AceListItem->DisplayString = NULL;
201 }
202 }
203 else
204 {
205 LSA_HANDLE LsaHandle;
206 NTSTATUS Status;
207
208 AceListItem->DisplayString = NULL;
209
210 /* read the domain of the SID */
211 if (OpenLSAPolicyHandle(NULL, /* FIXME */
212 POLICY_LOOKUP_NAMES | POLICY_VIEW_LOCAL_INFORMATION,
213 &LsaHandle))
214 {
215 PLSA_REFERENCED_DOMAIN_LIST ReferencedDomain;
216 PLSA_TRANSLATED_NAME Names;
217 PLSA_TRUST_INFORMATION Domain;
218 PLSA_UNICODE_STRING DomainName;
219 PPOLICY_ACCOUNT_DOMAIN_INFO PolicyAccountDomainInfo = NULL;
220
221 Status = LsaLookupSids(LsaHandle,
222 1,
223 &Sid,
224 &ReferencedDomain,
225 &Names);
226 if (NT_SUCCESS(Status))
227 {
228 if (ReferencedDomain != NULL &&
229 Names->DomainIndex >= 0)
230 {
231 Domain = &ReferencedDomain->Domains[Names->DomainIndex];
232 DomainName = &Domain->Name;
233 }
234 else
235 {
236 Domain = NULL;
237 DomainName = NULL;
238 }
239
240 AceListItem->SidNameUse = Names->Use;
241
242 switch (Names->Use)
243 {
244 case SidTypeAlias:
245 if (Domain != NULL)
246 {
247 /* query the domain name for BUILTIN accounts */
248 Status = LsaQueryInformationPolicy(LsaHandle,
249 PolicyAccountDomainInformation,
250 (PVOID*)&PolicyAccountDomainInfo);
251 if (NT_SUCCESS(Status))
252 {
253 DomainName = &PolicyAccountDomainInfo->DomainName;
254
255 /* make the user believe this is a group */
256 AceListItem->SidNameUse = SidTypeGroup;
257 }
258 }
259 /* fall through */
260
261 case SidTypeUser:
262 {
263 if (Domain != NULL)
264 {
265 AceListItem->DisplayString = (LPWSTR)LocalAlloc(LMEM_FIXED,
266 (AccountNameSize * sizeof(WCHAR)) +
267 (DomainName->Length + sizeof(WCHAR)) +
268 (Names->Name.Length + sizeof(WCHAR)) +
269 (4 * sizeof(WCHAR)));
270 if (AceListItem->DisplayString != NULL)
271 {
272 WCHAR *s;
273
274 /* NOTE: LSA_UNICODE_STRINGs are not always NULL-terminated! */
275
276 wcscpy(AceListItem->DisplayString,
277 AceListItem->AccountName);
278 wcscat(AceListItem->DisplayString,
279 L" (");
280 s = AceListItem->DisplayString + wcslen(AceListItem->DisplayString);
281 CopyMemory(s,
282 DomainName->Buffer,
283 DomainName->Length);
284 s += DomainName->Length / sizeof(WCHAR);
285 *(s++) = L'\\';
286 CopyMemory(s,
287 Names->Name.Buffer,
288 Names->Name.Length);
289 s += Names->Name.Length / sizeof(WCHAR);
290 *(s++) = L')';
291 *s = L'\0';
292 }
293
294 /* mark the ace as a user unless it's a
295 BUILTIN account */
296 if (PolicyAccountDomainInfo == NULL)
297 {
298 AceListItem->SidNameUse = SidTypeUser;
299 }
300 }
301 break;
302 }
303
304 case SidTypeWellKnownGroup:
305 {
306 /* make the user believe this is a group */
307 AceListItem->SidNameUse = SidTypeGroup;
308 break;
309 }
310
311 default:
312 {
313 break;
314 }
315 }
316
317 if (PolicyAccountDomainInfo != NULL)
318 {
319 LsaFreeMemory(PolicyAccountDomainInfo);
320 }
321
322 LsaFreeMemory(ReferencedDomain);
323 LsaFreeMemory(Names);
324 }
325 LsaClose(LsaHandle);
326 }
327 }
328
329 /* append item to the cached ACL */
330 *NextAcePtr = AceListItem;
331 NextAcePtr = &AceListItem->Next;
332 }
333 }
334 }
335 }
336 LocalFree((HLOCAL)SecurityDescriptor);
337 }
338 }
339
340 static INT
341 AddAceListEntry(IN PSECURITY_PAGE sp,
342 IN PACE_LISTITEM AceListItem,
343 IN INT Index,
344 IN BOOL Selected)
345 {
346 LVITEM li;
347
348 li.mask = LVIF_IMAGE | LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
349 li.iItem = Index;
350 li.iSubItem = 0;
351 li.state = (Selected ? LVIS_SELECTED : 0);
352 li.stateMask = LVIS_SELECTED;
353 li.pszText = (AceListItem->DisplayString != NULL ? AceListItem->DisplayString : AceListItem->AccountName);
354 switch (AceListItem->SidNameUse)
355 {
356 case SidTypeUser:
357 li.iImage = 0;
358 break;
359 case SidTypeGroup:
360 li.iImage = 1;
361 break;
362 default:
363 li.iImage = -1;
364 break;
365 }
366 li.lParam = (LPARAM)AceListItem;
367
368 return ListView_InsertItem(sp->hWndAceList,
369 &li);
370 }
371
372 static VOID
373 FillUsersGroupsList(IN PSECURITY_PAGE sp)
374 {
375 LPARAM SelLParam;
376 PACE_LISTITEM CurItem;
377 RECT rcLvClient;
378
379 SelLParam = ListViewGetSelectedItemData(sp->hWndAceList);
380
381 DisableRedrawWindow(sp->hWndAceList);
382
383 ListView_DeleteAllItems(sp->hWndAceList);
384
385 ReloadUsersGroupsList(sp);
386
387 for (CurItem = sp->AceListHead;
388 CurItem != NULL;
389 CurItem = CurItem->Next)
390 {
391 AddAceListEntry(sp,
392 CurItem,
393 -1,
394 (SelLParam == (LPARAM)CurItem));
395 }
396
397 EnableRedrawWindow(sp->hWndAceList);
398
399 GetClientRect(sp->hWndAceList, &rcLvClient);
400
401 ListView_SetColumnWidth(sp->hWndAceList,
402 0,
403 rcLvClient.right);
404 }
405
406 static VOID
407 UpdateControlStates(IN PSECURITY_PAGE sp)
408 {
409 BOOL UserOrGroupSelected;
410
411 UserOrGroupSelected = (ListViewGetSelectedItemData(sp->hWndAceList) != 0);
412
413 EnableWindow(sp->hBtnRemove, UserOrGroupSelected);
414 }
415
416 static UINT CALLBACK
417 SecurityPageCallback(IN HWND hwnd,
418 IN UINT uMsg,
419 IN LPPROPSHEETPAGE ppsp)
420 {
421 PSECURITY_PAGE sp = (PSECURITY_PAGE)ppsp->lParam;
422
423 switch(uMsg)
424 {
425 case PSPCB_CREATE:
426 {
427 return TRUE;
428 }
429 case PSPCB_RELEASE:
430 {
431 DestroySecurityPage(sp);
432 UnregisterCheckListControl();
433 return FALSE;
434 }
435 }
436
437 return FALSE;
438 }
439
440 static VOID
441 SetAceCheckListColumns(IN HWND hAceCheckList,
442 IN UINT Button,
443 IN HWND hLabel)
444 {
445 POINT pt;
446 RECT rcLabel;
447
448 GetWindowRect(hLabel,
449 &rcLabel);
450 pt.y = 0;
451 pt.x = (rcLabel.right - rcLabel.left) / 2;
452 MapWindowPoints(hLabel,
453 hAceCheckList,
454 &pt,
455 1);
456
457 SendMessage(hAceCheckList,
458 CLM_SETCHECKBOXCOLUMN,
459 Button,
460 pt.x);
461 }
462
463 static VOID
464 LoadPermissionsList(IN PSECURITY_PAGE sp,
465 IN GUID *GuidObjectType,
466 IN DWORD dwFlags,
467 OUT SI_ACCESS *DefaultAccess)
468 {
469 HRESULT hRet;
470 PSI_ACCESS AccessList;
471 ULONG nAccessList, DefaultAccessIndex;
472
473 /* clear the permissions list */
474
475 SendMessage(sp->hAceCheckList,
476 CLM_CLEAR,
477 0,
478 0);
479
480 /* query the access rights from the server */
481 hRet = sp->psi->lpVtbl->GetAccessRights(sp->psi,
482 GuidObjectType,
483 dwFlags,
484 &AccessList,
485 &nAccessList,
486 &DefaultAccessIndex);
487 if (SUCCEEDED(hRet) && nAccessList != 0)
488 {
489 LPCWSTR NameStr;
490 PSI_ACCESS CurAccess, LastAccess;
491 WCHAR NameBuffer[MAX_PATH];
492
493 /* save the default access rights to be used when adding ACEs later */
494 if (DefaultAccess != NULL)
495 {
496 *DefaultAccess = AccessList[DefaultAccessIndex];
497 }
498
499 LastAccess = AccessList + nAccessList;
500 for (CurAccess = &AccessList[0];
501 CurAccess != LastAccess;
502 CurAccess++)
503 {
504 if (CurAccess->dwFlags & dwFlags)
505 {
506 /* get the permission name, load it from a string table if necessary */
507 if (IS_INTRESOURCE(CurAccess->pszName))
508 {
509 if (!LoadString(sp->ObjectInfo.hInstance,
510 (UINT)((ULONG_PTR)CurAccess->pszName),
511 NameBuffer,
512 sizeof(NameBuffer) / sizeof(NameBuffer[0])))
513 {
514 LoadString(hDllInstance,
515 IDS_UNKNOWN,
516 NameBuffer,
517 sizeof(NameBuffer) / sizeof(NameBuffer[0]));
518 }
519 NameStr = NameBuffer;
520 }
521 else
522 {
523 NameStr = CurAccess->pszName;
524 }
525
526 SendMessage(sp->hAceCheckList,
527 CLM_ADDITEM,
528 CIS_DISABLED | CIS_NONE,
529 (LPARAM)NameStr);
530 }
531 }
532 }
533 }
534
535 static INT_PTR CALLBACK
536 SecurityPageProc(IN HWND hwndDlg,
537 IN UINT uMsg,
538 IN WPARAM wParam,
539 IN LPARAM lParam)
540 {
541 PSECURITY_PAGE sp;
542
543 switch(uMsg)
544 {
545 case WM_NOTIFY:
546 {
547 NMHDR *pnmh = (NMHDR*)lParam;
548 if (pnmh->idFrom == IDC_ACELIST)
549 {
550 sp = (PSECURITY_PAGE)GetWindowLongPtr(hwndDlg,
551 DWL_USER);
552 if (sp != NULL)
553 {
554 switch(pnmh->code)
555 {
556 case LVN_ITEMCHANGED:
557 {
558 UpdateControlStates(sp);
559 break;
560 }
561 }
562 }
563 }
564 break;
565 }
566
567 case WM_INITDIALOG:
568 {
569 sp = (PSECURITY_PAGE)((LPPROPSHEETPAGE)lParam)->lParam;
570 if(sp != NULL)
571 {
572 LV_COLUMN lvc;
573 RECT rcLvClient;
574
575 sp->hWnd = hwndDlg;
576 sp->hWndAceList = GetDlgItem(hwndDlg, IDC_ACELIST);
577 sp->hBtnRemove = GetDlgItem(hwndDlg, IDC_ACELIST_REMOVE);
578 sp->hAceCheckList = GetDlgItem(hwndDlg, IDC_ACE_CHECKLIST);
579
580 /* save the pointer to the structure */
581 SetWindowLongPtr(hwndDlg,
582 DWL_USER,
583 (DWORD_PTR)sp);
584
585 sp->hiUsrs = ImageList_LoadBitmap(hDllInstance,
586 MAKEINTRESOURCE(IDB_USRGRPIMAGES),
587 16,
588 3,
589 0);
590
591 /* setup the listview control */
592 ListView_SetExtendedListViewStyleEx(sp->hWndAceList,
593 LVS_EX_FULLROWSELECT,
594 LVS_EX_FULLROWSELECT);
595 ListView_SetImageList(sp->hWndAceList,
596 sp->hiUsrs,
597 LVSIL_SMALL);
598
599 GetClientRect(sp->hWndAceList, &rcLvClient);
600
601 /* add a column to the list view */
602 lvc.mask = LVCF_FMT | LVCF_WIDTH;
603 lvc.fmt = LVCFMT_LEFT;
604 lvc.cx = rcLvClient.right;
605 ListView_InsertColumn(sp->hWndAceList, 0, &lvc);
606
607 FillUsersGroupsList(sp);
608
609 ListViewSelectItem(sp->hWndAceList,
610 0);
611
612 /* calculate the columns of the allow/deny checkboxes */
613 SetAceCheckListColumns(sp->hAceCheckList,
614 CLB_ALLOW,
615 GetDlgItem(hwndDlg, IDC_LABEL_ALLOW));
616 SetAceCheckListColumns(sp->hAceCheckList,
617 CLB_DENY,
618 GetDlgItem(hwndDlg, IDC_LABEL_DENY));
619
620 /* FIXME - hide controls in case the flags aren't present */
621
622 LoadPermissionsList(sp,
623 NULL,
624 SI_ACCESS_GENERAL |
625 ((sp->ObjectInfo.dwFlags & SI_CONTAINER) ? SI_ACCESS_CONTAINER : 0),
626 &sp->DefaultAccess);
627 }
628 break;
629 }
630 }
631 return 0;
632 }
633
634
635 /*
636 * CreateSecurityPage EXPORTED
637 *
638 * @implemented
639 */
640 HPROPSHEETPAGE
641 WINAPI
642 CreateSecurityPage(IN LPSECURITYINFO psi)
643 {
644 PROPSHEETPAGE psp;
645 PSECURITY_PAGE sPage;
646 SI_OBJECT_INFO ObjectInfo;
647 HRESULT hRet;
648
649 if(psi == NULL)
650 {
651 SetLastError(ERROR_INVALID_PARAMETER);
652
653 DPRINT("No ISecurityInformation class passed!\n");
654 return NULL;
655 }
656
657 /* get the object information from the server interface */
658 hRet = psi->lpVtbl->GetObjectInformation(psi, &ObjectInfo);
659
660 if(FAILED(hRet))
661 {
662 SetLastError(hRet);
663
664 DPRINT("CreateSecurityPage() failed! Failed to query the object information!\n");
665 return NULL;
666 }
667
668 if (!RegisterCheckListControl(hDllInstance))
669 {
670 DPRINT("Registering the CHECKLIST_ACLUI class failed!\n");
671 return NULL;
672 }
673
674 sPage = HeapAlloc(GetProcessHeap(),
675 HEAP_ZERO_MEMORY,
676 sizeof(SECURITY_PAGE));
677 if (sPage == NULL)
678 {
679 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
680
681 DPRINT("Not enough memory to allocate a SECURITY_PAGE!\n");
682 return NULL;
683 }
684 sPage->psi = psi;
685 sPage->ObjectInfo = ObjectInfo;
686
687 ZeroMemory(&psp, sizeof(psp));
688
689 psp.dwSize = sizeof(PROPSHEETPAGE);
690 psp.dwFlags = PSP_USECALLBACK;
691 psp.hInstance = hDllInstance;
692 psp.pszTemplate = MAKEINTRESOURCE(IDD_SECPAGE);
693 psp.pfnDlgProc = SecurityPageProc;
694 psp.lParam = (LPARAM)sPage;
695 psp.pfnCallback = SecurityPageCallback;
696
697 if((ObjectInfo.dwFlags & SI_PAGE_TITLE) &&
698 ObjectInfo.pszPageTitle != NULL && ObjectInfo.pszPageTitle[0] != L'\0')
699 {
700 /* Set the page title if the flag is present and the string isn't empty */
701 psp.pszTitle = ObjectInfo.pszPageTitle;
702 psp.dwFlags |= PSP_USETITLE;
703 }
704
705 /* NOTE: the SECURITY_PAGE structure will be freed by the property page
706 callback! */
707
708 return CreatePropertySheetPage(&psp);
709 }
710
711
712 /*
713 * EditSecurity EXPORTED
714 *
715 * @implemented
716 */
717 BOOL
718 WINAPI
719 EditSecurity(IN HWND hwndOwner,
720 IN LPSECURITYINFO psi)
721 {
722 HRESULT hRet;
723 SI_OBJECT_INFO ObjectInfo;
724 PROPSHEETHEADER psh;
725 HPROPSHEETPAGE hPages[1];
726 LPWSTR lpCaption;
727 BOOL Ret;
728
729 if(psi == NULL)
730 {
731 SetLastError(ERROR_INVALID_PARAMETER);
732
733 DPRINT("No ISecurityInformation class passed!\n");
734 return FALSE;
735 }
736
737 /* get the object information from the server interface */
738 hRet = psi->lpVtbl->GetObjectInformation(psi, &ObjectInfo);
739
740 if(FAILED(hRet))
741 {
742 SetLastError(hRet);
743
744 DPRINT("GetObjectInformation() failed!\n");
745 return FALSE;
746 }
747
748 /* create the page */
749 hPages[0] = CreateSecurityPage(psi);
750 if(hPages[0] == NULL)
751 {
752 DPRINT("CreateSecurityPage(), couldn't create property sheet!\n");
753 return FALSE;
754 }
755
756 psh.dwSize = sizeof(PROPSHEETHEADER);
757 psh.dwFlags = PSH_DEFAULT;
758 psh.hwndParent = hwndOwner;
759 psh.hInstance = hDllInstance;
760 if((ObjectInfo.dwFlags & SI_PAGE_TITLE) &&
761 ObjectInfo.pszPageTitle != NULL && ObjectInfo.pszPageTitle[0] != L'\0')
762 {
763 /* Set the page title if the flag is present and the string isn't empty */
764 psh.pszCaption = ObjectInfo.pszPageTitle;
765 psh.dwFlags |= PSH_PROPTITLE;
766 lpCaption = NULL;
767 }
768 else
769 {
770 /* Set the page title to the object name, make sure the format string
771 has "%1" NOT "%s" because it uses FormatMessage() to automatically
772 allocate the right amount of memory. */
773 LoadAndFormatString(hDllInstance,
774 IDS_PSP_TITLE,
775 &lpCaption,
776 ObjectInfo.pszObjectName);
777 psh.pszCaption = lpCaption;
778 }
779
780 psh.nPages = sizeof(hPages) / sizeof(HPROPSHEETPAGE);
781 psh.nStartPage = 0;
782 psh.phpage = hPages;
783
784 Ret = (PropertySheet(&psh) != -1);
785
786 if(lpCaption != NULL)
787 {
788 LocalFree((HLOCAL)lpCaption);
789 }
790
791 return Ret;
792 }
793
794 BOOL STDCALL
795 DllMain(IN HINSTANCE hinstDLL,
796 IN DWORD dwReason,
797 IN LPVOID lpvReserved)
798 {
799 switch (dwReason)
800 {
801 case DLL_PROCESS_ATTACH:
802 hDllInstance = hinstDLL;
803 break;
804 case DLL_THREAD_ATTACH:
805 case DLL_THREAD_DETACH:
806 case DLL_PROCESS_DETACH:
807 break;
808 }
809 return TRUE;
810 }
811