From: Zebediah Figura Subject: [PATCH 1/2] aclui: Add basic ACE viewer. Message-Id: <20210218053217.369520-1-z.figura12@gmail.com> Date: Wed, 17 Feb 2021 23:32:16 -0600 From: Michael Müller Signed-off-by: Zebediah Figura --- Tested with Microsoft's winobj and by using the following patch. dlls/aclui/Makefile.in | 3 + dlls/aclui/aclui.rc | 56 +++++ dlls/aclui/aclui_main.c | 497 +++++++++++++++++++++++++++++++++++++- dlls/aclui/resource.h | 36 +++ dlls/aclui/user_icons.bmp | Bin 0 -> 2730 bytes 5 files changed, 587 insertions(+), 5 deletions(-) create mode 100644 dlls/aclui/aclui.rc create mode 100644 dlls/aclui/resource.h create mode 100644 dlls/aclui/user_icons.bmp diff --git a/dlls/aclui/Makefile.in b/dlls/aclui/Makefile.in index 83bd379c1ba..dc6a5e04843 100644 --- a/dlls/aclui/Makefile.in +++ b/dlls/aclui/Makefile.in @@ -1,6 +1,9 @@ MODULE = aclui.dll IMPORTLIB = aclui +IMPORTS = comctl32 user32 advapi32 gdi32 EXTRADLLFLAGS = -mno-cygwin C_SRCS = aclui_main.c + +RC_SRCS = aclui.rc diff --git a/dlls/aclui/aclui.rc b/dlls/aclui/aclui.rc new file mode 100644 index 00000000000..feccde4f35e --- /dev/null +++ b/dlls/aclui/aclui.rc @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 Michael Müller + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "resource.h" + +#define WINE_FILEDESCRIPTION_STR "Wine Security Descriptor Editor" +#define WINE_FILENAME_STR "aclui.dll" +#define WINE_FILEVERSION 5,1,2600,5512 +#define WINE_FILEVERSION_STR "5.1.2600.5512" +#define WINE_PRODUCTVERSION 5,1,2600,5512 +#define WINE_PRODUCTVERSION_STR "5.1" + +#include "wine/wine_common_ver.rc" + +#pragma makedep po + +LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT + +IDD_SECURITY_PROPERTIES DIALOGEX 0, 0, 240, 215 +STYLE DS_SHELLFONT | WS_CHILD | WS_CAPTION +CAPTION "Security" +FONT 8, "MS Shell Dlg" +BEGIN + LTEXT "&Group or user names:", -1, 5, 5, 230, 10 + CONTROL "", IDC_USERS, "SysListView32", LVS_REPORT | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | LVS_SORTASCENDING | LVS_SINGLESEL | LVS_SHOWSELALWAYS | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP, 5, 17, 230, 63, WS_EX_NOPARENTNOTIFY | WS_EX_CLIENTEDGE + + LTEXT "", IDC_ACE_USER, 5, 105, 110, 10 + LTEXT "Allow", -1, 120, 105, 55, 10, SS_CENTER + LTEXT "Deny", -1, 180, 105, 55, 10, SS_CENTER + CONTROL "", IDC_ACE, "SysListView32", LVS_REPORT | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | LVS_SINGLESEL | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP, 5, 115, 230, 95, WS_EX_NOPARENTNOTIFY | WS_EX_CLIENTEDGE +END + +STRINGTABLE +BEGIN + IDS_PERMISSION_FOR "Permissions for %1" +END + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL + +/* @makedep: user_icons.bmp */ +IDB_USER_ICONS BITMAP user_icons.bmp diff --git a/dlls/aclui/aclui_main.c b/dlls/aclui/aclui_main.c index 033a47191cb..15d0cc5788a 100644 --- a/dlls/aclui/aclui_main.c +++ b/dlls/aclui/aclui_main.c @@ -19,18 +19,51 @@ */ #include - +#define COBJMACROS +#define NONAMELESSUNION #include "initguid.h" #include "windef.h" #include "winbase.h" #include "winuser.h" #include "winnt.h" #include "aclui.h" +#include "resource.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(aclui); +/* the aclui.h files does not contain the necessary COBJMACROS */ +#define ISecurityInformation_AddRef(This) (This)->lpVtbl->AddRef(This) +#define ISecurityInformation_Release(This) (This)->lpVtbl->Release(This) +#define ISecurityInformation_GetObjectInformation(This, obj) (This)->lpVtbl->GetObjectInformation(This, obj) +#define ISecurityInformation_GetSecurity(This, info, sd, def) (This)->lpVtbl->GetSecurity(This, info, sd, def) +#define ISecurityInformation_GetAccessRights(This, type, flags, access, count, def) (This)->lpVtbl->GetAccessRights(This, type, flags, access, count, def) + +struct user +{ + WCHAR *name; + PSID sid; +}; + +struct security_page +{ + ISecurityInformation *security; + SI_OBJECT_INFO info; + PSECURITY_DESCRIPTOR sd; + + SI_ACCESS *access; + ULONG access_count; + + struct user *users; + unsigned int user_count; + + HWND dialog; + HIMAGELIST image_list_user; +}; + +static HINSTANCE aclui_instance; + BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { TRACE("(0x%p, %d, %p)\n", hinstDLL, fdwReason, lpvReserved); @@ -40,20 +73,474 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) case DLL_WINE_PREATTACH: return FALSE; /* prefer native version */ case DLL_PROCESS_ATTACH: + aclui_instance = hinstDLL; DisableThreadLibraryCalls(hinstDLL); break; } return TRUE; } -HPROPSHEETPAGE WINAPI CreateSecurityPage(LPSECURITYINFO psi) +static WCHAR *WINAPIV load_formatstr(UINT resource, ...) +{ + __ms_va_list valist; + WCHAR *str; + DWORD ret; + + __ms_va_start(valist, resource); + ret = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE, + aclui_instance, resource, 0, (WCHAR*)&str, 0, &valist); + __ms_va_end(valist); + return ret ? str : NULL; +} + +static void security_page_free(struct security_page *page) +{ + unsigned int i; + + for (i = 0; i < page->user_count; ++i) + free(page->users[i].name); + free(page->users); + + LocalFree(page->sd); + if (page->image_list_user) ImageList_Destroy(page->image_list_user); + if (page->security) ISecurityInformation_Release(page->security); + free(page); +} + +static WCHAR *get_sid_name(PSID sid, SID_NAME_USE *sid_type) { - FIXME("(%p): stub\n", psi); + WCHAR *name, *domain; + DWORD domain_len = 0; + DWORD name_len = 0; + BOOL ret; + + LookupAccountSidW(NULL, sid, NULL, &name_len, NULL, &domain_len, sid_type); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return NULL; + if (!(name = malloc(name_len * sizeof(WCHAR)))) + return NULL; + if (!(domain = malloc(domain_len * sizeof(WCHAR)))) + { + free(name); + return NULL; + } + + ret = LookupAccountSidW(NULL, sid, name, &name_len, domain, &domain_len, sid_type); + free(domain); + if (ret) return name; + free(name); return NULL; } -BOOL WINAPI EditSecurity(HWND owner, LPSECURITYINFO psi) +static void users_add(struct security_page *page, PSID sid) +{ + struct user *new_array, *user; + SID_NAME_USE sid_type; + unsigned int i; + LVITEMW item; + WCHAR *name; + + /* check if we already processed this user or group */ + for (i = 0; i < page->user_count; ++i) + { + if (EqualSid(sid, page->users[i].sid)) + return; + } + + if (!(name = get_sid_name(sid, &sid_type))) + return; + + if (!(new_array = realloc(page->users, (page->user_count + 1) * sizeof(*page->users)))) + return; + page->users = new_array; + user = &page->users[page->user_count++]; + + user->name = name; + user->sid = sid; + + /* Add in GUI */ + item.mask = LVIF_PARAM | LVIF_TEXT; + item.iItem = -1; + item.iSubItem = 0; + item.pszText = name; + item.lParam = (LPARAM)user; + + if (page->image_list_user) + { + item.mask |= LVIF_IMAGE; + item.iImage = (sid_type == SidTypeGroup || sid_type == SidTypeWellKnownGroup) ? 0 : 1; + } + + SendMessageW(GetDlgItem(page->dialog, IDC_USERS), LVM_INSERTITEMW, 0, (LPARAM)&item); +} + +static PSID get_sid_from_ace(ACE_HEADER *ace) +{ + switch (ace->AceType) + { + case ACCESS_ALLOWED_ACE_TYPE: + return (SID *)&((ACCESS_ALLOWED_ACE *)ace)->SidStart; + case ACCESS_DENIED_ACE_TYPE: + return (SID *)&((ACCESS_DENIED_ACE *)ace)->SidStart; + default: + FIXME("Don't know how to extract SID from ace type %d\n", ace->AceType); + return NULL; + } +} + +static void users_refresh(struct security_page *page) +{ + BOOL defaulted, present; + ACE_HEADER *ace; + unsigned int i; + DWORD index; + ACL *dacl; + PSID sid; + + for (i = 0; i < page->user_count; ++i) + free(page->users[i].name); + free(page->users); + + if (!GetSecurityDescriptorOwner(page->sd, &sid, &defaulted) + || !GetSecurityDescriptorDacl(page->sd, &present, &dacl, &defaulted)) + { + ERR("Failed to query descriptor information, error %u.\n", GetLastError()); + return; + } + + page->user_count = 1; + if (present) + page->user_count += dacl->AceCount; + if (!(page->users = malloc(page->user_count * sizeof(*page->users)))) + return; + + users_add(page, sid); + + /* Everyone else who appears in the DACL */ + if (GetSecurityDescriptorDacl(page->sd, &present, &dacl, &defaulted) && present) + { + for (index = 0; index < dacl->AceCount; index++) + { + if (!GetAce(dacl, index, (void**)&ace)) + break; + if (!(sid = get_sid_from_ace(ace))) + continue; + users_add(page, sid); + } + } +} + +static HIMAGELIST create_image_list(UINT resource, UINT width, UINT height, UINT count, COLORREF mask_color) +{ + HIMAGELIST image_list; + HBITMAP image; + INT ret; + + if (!(image_list = ImageList_Create(width, height, ILC_COLOR32 | ILC_MASK, 0, count))) + return NULL; + if (!(image = LoadBitmapW(aclui_instance, MAKEINTRESOURCEW(resource)))) + goto error; + + ret = ImageList_AddMasked(image_list, image, mask_color); + DeleteObject(image); + if (ret != -1) return image_list; + +error: + ImageList_Destroy(image_list); + return NULL; +} + +static void compute_access_masks(PSECURITY_DESCRIPTOR sd, PSID sid, ACCESS_MASK *allowed, ACCESS_MASK *denied) +{ + BOOL defaulted, present; + ACE_HEADER *ace; + PSID ace_sid; + DWORD index; + ACL *dacl; + + *allowed = 0; + *denied = 0; + + if (!GetSecurityDescriptorDacl(sd, &present, &dacl, &defaulted) || !present) + return; + + for (index = 0; index < dacl->AceCount; index++) + { + if (!GetAce(dacl, index, (void**)&ace)) + break; + + ace_sid = get_sid_from_ace(ace); + if (!ace_sid || !EqualSid(ace_sid, sid)) + continue; + + if (ace->AceType == ACCESS_ALLOWED_ACE_TYPE) + *allowed |= ((ACCESS_ALLOWED_ACE*)ace)->Mask; + else if (ace->AceType == ACCESS_DENIED_ACE_TYPE) + *denied |= ((ACCESS_DENIED_ACE*)ace)->Mask; + } +} + +static void show_ace_entries(struct security_page *page, struct user *user) +{ + ACCESS_MASK allowed, denied; + WCHAR *infotext; + ULONG i, index; + LVITEMW item; + HWND control; + + compute_access_masks(page->sd, user->sid, &allowed, &denied); + + if ((infotext = load_formatstr(IDS_PERMISSION_FOR, user->name))) + { + SetDlgItemTextW(page->dialog, IDC_ACE_USER, infotext); + LocalFree(infotext); + } + + control = GetDlgItem(page->dialog, IDC_ACE); + index = 0; + for (i = 0; i < page->access_count; i++) + { + if (!(page->access[i].dwFlags & SI_ACCESS_GENERAL)) + continue; + + item.mask = LVIF_TEXT; + item.iItem = index; + + item.iSubItem = 1; + if ((page->access[i].mask & allowed) == page->access[i].mask) + item.pszText = (WCHAR *)L"X"; + else + item.pszText = (WCHAR *)L"-"; + SendMessageW(control, LVM_SETITEMW, 0, (LPARAM)&item); + + item.iSubItem = 2; + if ((page->access[i].mask & denied) == page->access[i].mask) + item.pszText = (WCHAR *)L"X"; + else + item.pszText = (WCHAR *)L"-"; + SendMessageW(control, LVM_SETITEMW, 0, (LPARAM)&item); + + index++; + } +} + +static void create_ace_entries(struct security_page *page) +{ + WCHAR str[256]; + HWND control; + LVITEMW item; + ULONG i, index; + + control = GetDlgItem(page->dialog, IDC_ACE); + index = 0; + for (i = 0; i < page->access_count; i++) + { + if (!(page->access[i].dwFlags & SI_ACCESS_GENERAL)) + continue; + + item.mask = LVIF_TEXT; + item.iItem = index; + item.iSubItem = 0; + if (IS_INTRESOURCE(page->access[i].pszName)) + { + str[0] = 0; + LoadStringW(page->info.hInstance, (UINT)(DWORD_PTR)page->access[i].pszName, str, 256); + item.pszText = str; + } + else + item.pszText = (WCHAR *)page->access[i].pszName; + SendMessageW(control, LVM_INSERTITEMW, 0, (LPARAM)&item); + + index++; + } +} + +static void security_page_init_dlg(HWND hwnd, struct security_page *page) { - FIXME("(%p, %p): stub\n", owner, psi); + LVCOLUMNW column; + HWND control; + RECT rect; + ULONG def = 0; + HRESULT hr; + + page->dialog = hwnd; + + if (FAILED(hr = ISecurityInformation_GetSecurity(page->security, DACL_SECURITY_INFORMATION + | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, &page->sd, FALSE))) + { + ERR("Failed to get security descriptor, hr %#x.\n", hr); + return; + } + + if (FAILED(hr = ISecurityInformation_GetAccessRights(page->security, + NULL, 0, &page->access, &page->access_count, &def))) + { + ERR("Failed to get access mapping, hr %#x.\n", hr); + return; + } + + /* Prepare user list */ + control = GetDlgItem(hwnd, IDC_USERS); + SendMessageW(control, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT); + + GetClientRect(control, &rect); + column.mask = LVCF_FMT | LVCF_WIDTH; + column.fmt = LVCFMT_LEFT; + column.cx = rect.right - rect.left; + SendMessageW(control, LVM_INSERTCOLUMNW, 0, (LPARAM)&column); + + if ((page->image_list_user = create_image_list(IDB_USER_ICONS, 18, 18, 2, RGB(255, 0, 255)))) + SendMessageW(control, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM)page->image_list_user); + + /* Prepare ACE list */ + control = GetDlgItem(hwnd, IDC_ACE); + SendMessageW(control, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT); + + column.mask = LVCF_FMT | LVCF_WIDTH; + column.fmt = LVCFMT_LEFT; + column.cx = 170; + SendMessageW(control, LVM_INSERTCOLUMNW, 0, (LPARAM)&column); + + column.mask = LVCF_FMT | LVCF_WIDTH; + column.fmt = LVCFMT_CENTER; + column.cx = 85; + SendMessageW(control, LVM_INSERTCOLUMNW, 1, (LPARAM)&column); + + column.mask = LVCF_FMT | LVCF_WIDTH; + column.fmt = LVCFMT_CENTER; + column.cx = 85; + SendMessageW(control, LVM_INSERTCOLUMNW, 2, (LPARAM)&column); + + users_refresh(page); + create_ace_entries(page); + + if (page->user_count) + { + LVITEMW item; + item.mask = LVIF_STATE; + item.iItem = 0; + item.iSubItem = 0; + item.state = LVIS_FOCUSED | LVIS_SELECTED; + item.stateMask = item.state; + SendMessageW(GetDlgItem(hwnd, IDC_USERS), LVM_SETITEMW, 0, (LPARAM)&item); + } +} + +static INT_PTR CALLBACK security_page_proc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_INITDIALOG: + { + PROPSHEETPAGEW *ppsp = (PROPSHEETPAGEW *)lParam; + SetWindowLongPtrW(hwndDlg, DWLP_USER, (LONG_PTR)ppsp->lParam); + security_page_init_dlg(hwndDlg, (struct security_page *)ppsp->lParam); + break; + } + + case WM_NOTIFY: + { + struct security_page *page = (struct security_page *)GetWindowLongPtrW(hwndDlg, DWLP_USER); + NMHDR *hdr = (NMHDR *)lParam; + + if (hdr->hwndFrom == GetDlgItem(hwndDlg, IDC_USERS) && hdr->code == LVN_ITEMCHANGED) + { + NMLISTVIEW *nmv = (NMLISTVIEW *)lParam; + if (!(nmv->uOldState & LVIS_SELECTED) && (nmv->uNewState & LVIS_SELECTED)) + show_ace_entries(page, (struct user *)nmv->lParam); + return TRUE; + } + break; + } + + default: + break; + } return FALSE; } + +static UINT CALLBACK security_page_callback(HWND hwnd, UINT msg, PROPSHEETPAGEW *ppsp) +{ + struct security_page *page = (struct security_page *)ppsp->lParam; + + if (msg == PSPCB_RELEASE) + security_page_free(page); + + return 1; +} + +HPROPSHEETPAGE WINAPI CreateSecurityPage(ISecurityInformation *security) +{ + struct security_page *page; + PROPSHEETPAGEW propsheet; + HPROPSHEETPAGE ret; + + TRACE("%p\n", security); + + InitCommonControls(); + + if (!(page = calloc(1, sizeof(*page)))) + return NULL; + + if (FAILED(ISecurityInformation_GetObjectInformation(security, &page->info))) + { + free(page); + return NULL; + } + + page->security = security; + ISecurityInformation_AddRef(security); + + memset(&propsheet, 0, sizeof(propsheet)); + propsheet.dwSize = sizeof(propsheet); + propsheet.dwFlags = PSP_DEFAULT | PSP_USECALLBACK; + propsheet.hInstance = aclui_instance; + propsheet.u.pszTemplate = (WCHAR *)MAKEINTRESOURCE(IDD_SECURITY_PROPERTIES); + propsheet.pfnDlgProc = security_page_proc; + propsheet.pfnCallback = security_page_callback; + propsheet.lParam = (LPARAM)page; + + if (page->info.dwFlags & SI_PAGE_TITLE) + { + propsheet.pszTitle = page->info.pszPageTitle; + propsheet.dwFlags |= PSP_USETITLE; + } + + if (!(ret = CreatePropertySheetPageW(&propsheet))) + { + ERR("Failed to create property sheet page.\n"); + ISecurityInformation_Release(security); + free(page); + return NULL; + } + return ret; +} + +BOOL WINAPI EditSecurity(HWND owner, LPSECURITYINFO psi) +{ + PROPSHEETHEADERW prop; + HPROPSHEETPAGE pages[1]; + SI_OBJECT_INFO info; + BOOL ret; + + TRACE("(%p, %p)\n", owner, psi); + + if (FAILED(ISecurityInformation_GetObjectInformation(psi, &info))) + return FALSE; + if (!(pages[0] = CreateSecurityPage(psi))) + return FALSE; + + memset(&prop, 0, sizeof(prop)); + prop.dwSize = sizeof(prop); + prop.dwFlags = PSH_DEFAULT; + prop.hwndParent = owner; + prop.hInstance = aclui_instance; + prop.pszCaption = load_formatstr(IDS_PERMISSION_FOR, info.pszObjectName); + prop.nPages = 1; + prop.u2.nStartPage = 0; + prop.u3.phpage = pages; + + ret = PropertySheetW(&prop) != -1; + LocalFree((void *)prop.pszCaption); + return ret; +} diff --git a/dlls/aclui/resource.h b/dlls/aclui/resource.h new file mode 100644 index 00000000000..f92d99692c7 --- /dev/null +++ b/dlls/aclui/resource.h @@ -0,0 +1,36 @@ +/* + * Definitions for aclui dialog controls + * + * Copyright (c) 2017 Michael Müller + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __WINE_ACLUI__ +#define __WINE_ACLUI__ + +#define IDD_SECURITY_PROPERTIES 100 + +#define IDC_USERS 101 + +#define IDC_ACE_USER 110 +#define IDC_ACE 111 + +#define IDS_PERMISSION_FOR 1000 + +#define IDB_USER_ICONS 2000 +#define IDB_CHECKBOX 2001 + +#endif /* __WINE_ACLUI__ */ diff --git a/dlls/aclui/user_icons.bmp b/dlls/aclui/user_icons.bmp new file mode 100644 index 0000000000000000000000000000000000000000..29259e2724ccf67b7e3e84c3fc6d749bc9e8ae26 GIT binary patch literal 2730 zcmdUtc~F#P0LH1N8IS3YY|3(`$+FC$Q3M3!8WF=I!cj3Jj^t)pSCB(Uu2clUR2&33 zmt$E#SWXd>M2$=FNK;cX1Ox;D6*{J|rkY>xw}vCAo72=EeY4NL`+nd1{Py{t_qBB> zousF|c8KpNp*Mx52tB1YUGEt&P8ZJ&pS>qB9lg-gyZ_O%U9u`<^ud#kPmfy&di<}{ zakhb0O*)MQyY8!{B!$ke8g;%$TT?AHpKYKjQ9KWlM+A%)TIkK`T6*g}5L`zZYvknL+p)?dmLW4gBmX{G?_dsa{rOyBm_@A>?UD@ut;exz8p2V*`?1=8)iGhP3WM|43gK1tH7D z8bj?`$*&atY)>N!!&Z{9b}ngd2BZl8)|L9C$rjUnv2_eX;$7(MM3$P2bJ7mhChsOT z=mS(TbJRY@6#LC%zs!Utv2O)po-5eN&a=t#v*MUCZVaonEFE>gyQI1rpz=1>`qjSk z(1bfsn;cG_mytG~lq=p*pDej8P3ojEj2pLaVHO&|OY6Ov?B~V=9~T_mY|#WQL?bt) zYP}h?p*9TO?4xFrJ%xfVWy(|7^3BomF{~!lPD(-+YqOMycaZ#LLdVm>Idf1&k$5*L zr_`C0DQBH6m+H&LFk;ksOk3y41SyOES|-TdnIdy0dA*&;V$RV|9jHu{Q+uqL^3Q@f z6yt!}-<-;AK4Umi&wW?VQyR9MQhyUF6lNRIUT3!80_s7`OHM$Cggh?k>h28 zI&>v>bAK4a3Cqpm!|HWx_>As^dUy|$rfgQ z5_m7iMmdusH!x|VKa;{0%naI$S!Nq^vaX|_ah;iIU6?7`*?r{lL!8k(BmLcIBGynF zX2<4D3M^wQuuMKJbOwvKgKUWoK@+-+k_hL=GS=*jBB1c!%VnsgGA+-_zGkK{AX>BfBTSv(`R z^T+hxAIrGw{wwxzo0;kq%5=X}UXy3?x;z7;T_^B8G{~M)BgD7d;ls+Cyd^qvEbe$L zYqY1unO|d`-=lp88+9+HBA4WyExKNmed%xg5y~3gP-HVBxR4n^g-rEHV^-X86m>)F zJu^bWiD8^Xp4pk#kyUl+