1/*
2  Copyright (c) 1990-2007 Info-ZIP.  All rights reserved.
3
4  See the accompanying file LICENSE, version 2000-Apr-09 or later
5  (the contents of which are also included in unzip.h) for terms of use.
6  If, for some reason, all these files are missing, the Info-ZIP license
7  also may be found at:  ftp://ftp.info-zip.org/pub/infozip/license.html
8*/
9/*
10
11  Copyright (c) 1996  Scott Field (dedicated to Info-Zip group)
12
13  Module Name:
14
15    nt.c
16
17  Abstract:
18
19    This module implements WinNT security descriptor operations for the
20    Win32 Info-ZIP project.  Operation such as setting file security,
21    using/querying local and remote privileges, and queuing of operations
22    is performed here.  The contents of this module are only relevant
23    when the code is running on Windows NT, and the target volume supports
24    persistent Acl storage.
25
26    User privileges that allow accessing certain privileged aspects of the
27    security descriptor (such as the Sacl) are only used if the user specified
28    to do so.
29
30  Author:
31
32    Scott Field (sfield@microsoft.com)
33
34  Last revised:  18 Jan 97
35
36 */
37
38#define WIN32_LEAN_AND_MEAN
39#define UNZIP_INTERNAL
40#include "../unzip.h"
41#include <windows.h>
42#ifdef __RSXNT__
43#  include "../win32/rsxntwin.h"
44#endif
45#include "../win32/nt.h"
46
47
48#ifdef NTSD_EAS         /* This file is only needed for NTSD handling */
49
50/* Borland C++ does not define FILE_SHARE_DELETE. Others also? */
51#ifndef FILE_SHARE_DELETE
52#  define FILE_SHARE_DELETE 0x00000004
53#endif
54
55/* This macro definition is missing in old versions of MS' winbase.h. */
56#ifndef InterlockedExchangePointer
57#  define InterlockedExchangePointer(Target, Value) \
58      (PVOID)InterlockedExchange((PLONG)(Target), (LONG)(Value))
59#endif
60
61
62/* private prototypes */
63
64static BOOL Initialize(VOID);
65static VOID GetRemotePrivilegesSet(CHAR *FileName, PDWORD dwRemotePrivileges);
66static VOID InitLocalPrivileges(VOID);
67
68
69volatile BOOL bInitialized = FALSE; /* module level stuff initialized? */
70HANDLE hInitMutex = NULL;           /* prevent multiple initialization */
71
72BOOL g_bRestorePrivilege = FALSE;   /* for local set file security override */
73BOOL g_bSaclPrivilege = FALSE;      /* for local set sacl operations, only when
74                                       restore privilege not present */
75
76/* our single cached volume capabilities structure that describes the last
77   volume root we encountered.  A single entry like this works well in the
78   zip/unzip scenario for a number of reasons:
79   1. typically one extraction path during unzip.
80   2. typically process one volume at a time during zip, and then move
81      on to the next.
82   3. no cleanup code required and no memory leaks.
83   4. simple code.
84
85   This approach should be reworked to a linked list approach if we expect to
86   be called by many threads which are processing a variety of input/output
87   volumes, since lock contention and stale data may become a bottleneck. */
88
89VOLUMECAPS g_VolumeCaps;
90CRITICAL_SECTION VolumeCapsLock;
91
92
93static BOOL Initialize(VOID)
94{
95    HANDLE hMutex;
96    HANDLE hOldMutex;
97
98    if (bInitialized) return TRUE;
99
100    hMutex = CreateMutex(NULL, TRUE, NULL);
101    if(hMutex == NULL) return FALSE;
102
103    hOldMutex = (HANDLE)InterlockedExchangePointer((void *)&hInitMutex,
104                                                   hMutex);
105
106    if (hOldMutex != NULL) {
107        /* somebody setup the mutex already */
108        InterlockedExchangePointer((void *)&hInitMutex,
109                                   hOldMutex);
110
111        CloseHandle(hMutex); /* close new, un-needed mutex */
112
113        /* wait for initialization to complete and return status */
114        WaitForSingleObject(hOldMutex, INFINITE);
115        ReleaseMutex(hOldMutex);
116
117        return bInitialized;
118    }
119
120    if (!bInitialized) {
121        /* initialize module level resources */
122
123        InitializeCriticalSection( &VolumeCapsLock );
124        memset(&g_VolumeCaps, 0, sizeof(VOLUMECAPS));
125
126        InitLocalPrivileges();
127
128        bInitialized = TRUE;
129    }
130
131    InterlockedExchangePointer((void *)&hInitMutex,
132                               NULL);
133
134    ReleaseMutex(hMutex); /* release correct mutex */
135
136    CloseHandle(hMutex);  /* free the no longer needed handle resource */
137
138    return TRUE;
139}
140
141
142BOOL ValidateSecurity(uch *securitydata)
143{
144    PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR)securitydata;
145    PACL pAcl;
146    PSID pSid;
147    BOOL bAclPresent;
148    BOOL bDefaulted;
149
150    if(!IsWinNT()) return TRUE; /* don't do anything if not on WinNT */
151
152    if(!IsValidSecurityDescriptor(sd)) return FALSE;
153
154    /* verify Dacl integrity */
155
156    if(!GetSecurityDescriptorDacl(sd, &bAclPresent, &pAcl, &bDefaulted))
157        return FALSE;
158
159    if(bAclPresent && pAcl!=NULL) {
160        if(!IsValidAcl(pAcl)) return FALSE;
161    }
162
163    /* verify Sacl integrity */
164
165    if(!GetSecurityDescriptorSacl(sd, &bAclPresent, &pAcl, &bDefaulted))
166        return FALSE;
167
168    if(bAclPresent && pAcl!=NULL) {
169        if(!IsValidAcl(pAcl)) return FALSE;
170    }
171
172    /* verify owner integrity */
173
174    if(!GetSecurityDescriptorOwner(sd, &pSid, &bDefaulted))
175        return FALSE;
176
177    if(pSid != NULL) {
178        if(!IsValidSid(pSid)) return FALSE;
179    }
180
181    /* verify group integrity */
182
183    if(!GetSecurityDescriptorGroup(sd, &pSid, &bDefaulted))
184        return FALSE;
185
186    if(pSid != NULL) {
187        if(!IsValidSid(pSid)) return FALSE;
188    }
189
190    return TRUE;
191}
192
193static VOID GetRemotePrivilegesSet(char *FileName, PDWORD dwRemotePrivileges)
194{
195    HANDLE hFile;
196
197    *dwRemotePrivileges = 0;
198
199    /* see if we have the SeRestorePrivilege */
200
201    hFile = CreateFileA(
202        FileName,
203        ACCESS_SYSTEM_SECURITY | WRITE_DAC | WRITE_OWNER | READ_CONTROL,
204        FILE_SHARE_READ | FILE_SHARE_DELETE, /* no sd updating allowed here */
205        NULL,
206        OPEN_EXISTING,
207        FILE_FLAG_BACKUP_SEMANTICS,
208        NULL
209        );
210
211    if(hFile != INVALID_HANDLE_VALUE) {
212        /* no remote way to determine SeRestorePrivilege -- just try a
213           read/write to simulate it */
214        SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION |
215          SACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION |
216          GROUP_SECURITY_INFORMATION;
217        PSECURITY_DESCRIPTOR sd;
218        DWORD cbBuf = 0;
219
220        GetKernelObjectSecurity(hFile, si, NULL, cbBuf, &cbBuf);
221
222        if(ERROR_INSUFFICIENT_BUFFER == GetLastError()) {
223            if((sd = HeapAlloc(GetProcessHeap(), 0, cbBuf)) != NULL) {
224                if(GetKernelObjectSecurity(hFile, si, sd, cbBuf, &cbBuf)) {
225                    if(SetKernelObjectSecurity(hFile, si, sd))
226                        *dwRemotePrivileges |= OVERRIDE_RESTORE;
227                }
228                HeapFree(GetProcessHeap(), 0, sd);
229            }
230        }
231
232        CloseHandle(hFile);
233    } else {
234
235        /* see if we have the SeSecurityPrivilege */
236        /* note we don't need this if we have SeRestorePrivilege */
237
238        hFile = CreateFileA(
239            FileName,
240            ACCESS_SYSTEM_SECURITY,
241            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, /* max */
242            NULL,
243            OPEN_EXISTING,
244            0,
245            NULL
246            );
247
248        if(hFile != INVALID_HANDLE_VALUE) {
249            CloseHandle(hFile);
250            *dwRemotePrivileges |= OVERRIDE_SACL;
251        }
252    }
253}
254
255
256BOOL GetVolumeCaps(
257    char *rootpath,         /* filepath, or NULL */
258    char *name,             /* filename associated with rootpath */
259    PVOLUMECAPS VolumeCaps  /* result structure describing capabilities */
260    )
261{
262    char TempRootPath[MAX_PATH + 1];
263    DWORD cchTempRootPath = 0;
264    BOOL bSuccess = TRUE;   /* assume success until told otherwise */
265
266    if(!bInitialized) if(!Initialize()) return FALSE;
267
268    /* process the input path to produce a consistent path suitable for
269       compare operations and also suitable for certain picky Win32 API
270       that don't like forward slashes */
271
272    if(rootpath != NULL && rootpath[0] != '\0') {
273        DWORD i;
274
275        cchTempRootPath = lstrlenA(rootpath);
276        if(cchTempRootPath > MAX_PATH) return FALSE;
277
278        /* copy input, converting forward slashes to back slashes as we go */
279
280        for(i = 0 ; i <= cchTempRootPath ; i++) {
281            if(rootpath[i] == '/') TempRootPath[i] = '\\';
282            else TempRootPath[i] = rootpath[i];
283        }
284
285        /* check for UNC and Null terminate or append trailing \ as
286           appropriate */
287
288        /* possible valid UNCs we are passed follow:
289           \\machine\foo\bar (path is \\machine\foo\)
290           \\machine\foo     (path is \\machine\foo\)
291           \\machine\foo\
292           \\.\c$\     (FIXFIX: Win32API doesn't like this - GetComputerName())
293           LATERLATER: handling mounted DFS drives in the future will require
294                       slightly different logic which isn't available today.
295                       This is required because directories can point at
296                       different servers which have differing capabilities.
297         */
298
299        if(TempRootPath[0] == '\\' && TempRootPath[1] == '\\') {
300            DWORD slash = 0;
301
302            for(i = 2 ; i < cchTempRootPath ; i++) {
303                if(TempRootPath[i] == '\\') {
304                    slash++;
305
306                    if(slash == 2) {
307                        i++;
308                        TempRootPath[i] = '\0';
309                        cchTempRootPath = i;
310                        break;
311                    }
312                }
313            }
314
315            /* if there was only one slash found, just tack another onto the
316               end */
317
318            if(slash == 1 && TempRootPath[cchTempRootPath] != '\\') {
319                TempRootPath[cchTempRootPath] = TempRootPath[0]; /* '\\' */
320                TempRootPath[cchTempRootPath+1] = '\0';
321                cchTempRootPath++;
322            }
323
324        } else {
325
326            if(TempRootPath[1] == ':') {
327
328                /* drive letter specified, truncate to root */
329                TempRootPath[2] = '\\';
330                TempRootPath[3] = '\0';
331                cchTempRootPath = 3;
332            } else {
333
334                /* must be file on current drive */
335                TempRootPath[0] = '\0';
336                cchTempRootPath = 0;
337            }
338
339        }
340
341    } /* if path != NULL */
342
343    /* grab lock protecting cached entry */
344    EnterCriticalSection( &VolumeCapsLock );
345
346    if(!g_VolumeCaps.bValid ||
347       lstrcmpiA(g_VolumeCaps.RootPath, TempRootPath) != 0)
348    {
349
350        /* no match found, build up new entry */
351
352        DWORD dwFileSystemFlags;
353        DWORD dwRemotePrivileges = 0;
354        BOOL bRemote = FALSE;
355
356        /* release lock during expensive operations */
357        LeaveCriticalSection( &VolumeCapsLock );
358
359        bSuccess = GetVolumeInformationA(
360            (TempRootPath[0] == '\0') ? NULL : TempRootPath,
361            NULL, 0,
362            NULL, NULL,
363            &dwFileSystemFlags,
364            NULL, 0);
365
366
367        /* only if target volume supports Acls, and we were told to use
368           privileges do we need to go out and test for the remote case */
369
370        if(bSuccess && (dwFileSystemFlags & FS_PERSISTENT_ACLS) &&
371           VolumeCaps->bUsePrivileges)
372        {
373            if(GetDriveTypeA( (TempRootPath[0] == '\0') ? NULL : TempRootPath )
374               == DRIVE_REMOTE)
375            {
376                bRemote = TRUE;
377
378                /* make a determination about our remote capabilities */
379
380                GetRemotePrivilegesSet(name, &dwRemotePrivileges);
381            }
382        }
383
384        /* always take the lock again, since we release it below */
385        EnterCriticalSection( &VolumeCapsLock );
386
387        /* replace the existing data if successful */
388        if(bSuccess) {
389
390            lstrcpynA(g_VolumeCaps.RootPath, TempRootPath, cchTempRootPath+1);
391            g_VolumeCaps.dwFileSystemFlags = dwFileSystemFlags;
392            g_VolumeCaps.bRemote = bRemote;
393            g_VolumeCaps.dwRemotePrivileges = dwRemotePrivileges;
394            g_VolumeCaps.bValid = TRUE;
395        }
396    }
397
398    if(bSuccess) {
399        /* copy input elements */
400        g_VolumeCaps.bUsePrivileges = VolumeCaps->bUsePrivileges;
401        g_VolumeCaps.dwFileAttributes = VolumeCaps->dwFileAttributes;
402
403        /* give caller results */
404        memcpy(VolumeCaps, &g_VolumeCaps, sizeof(VOLUMECAPS));
405    } else {
406        g_VolumeCaps.bValid = FALSE;
407    }
408
409    LeaveCriticalSection( &VolumeCapsLock ); /* release lock */
410
411    return bSuccess;
412}
413
414
415BOOL SecuritySet(char *resource, PVOLUMECAPS VolumeCaps, uch *securitydata)
416{
417    HANDLE hFile;
418    DWORD dwDesiredAccess = 0;
419    DWORD dwFlags = 0;
420    PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR)securitydata;
421    SECURITY_DESCRIPTOR_CONTROL sdc;
422    SECURITY_INFORMATION RequestedInfo = 0;
423    DWORD dwRev;
424    BOOL bRestorePrivilege = FALSE;
425    BOOL bSaclPrivilege = FALSE;
426    BOOL bSuccess;
427
428    if(!bInitialized) if(!Initialize()) return FALSE;
429
430    /* defer directory processing */
431
432    if(VolumeCaps->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
433        /* opening a directory requires FILE_FLAG_BACKUP_SEMANTICS */
434        dwFlags |= FILE_FLAG_BACKUP_SEMANTICS;
435    }
436
437    /* evaluate the input security descriptor and act accordingly */
438
439    if(!IsValidSecurityDescriptor(sd))
440        return FALSE;
441
442    if(!GetSecurityDescriptorControl(sd, &sdc, &dwRev))
443        return FALSE;
444
445    /* setup privilege usage based on if told we can use privileges, and if so,
446       what privileges we have */
447
448    if(VolumeCaps->bUsePrivileges) {
449        if(VolumeCaps->bRemote) {
450            /* use remotely determined privileges */
451            if(VolumeCaps->dwRemotePrivileges & OVERRIDE_RESTORE)
452                bRestorePrivilege = TRUE;
453
454            if(VolumeCaps->dwRemotePrivileges & OVERRIDE_SACL)
455                bSaclPrivilege = TRUE;
456
457        } else {
458            /* use local privileges */
459            bRestorePrivilege = g_bRestorePrivilege;
460            bSaclPrivilege = g_bSaclPrivilege;
461        }
462    }
463
464
465    /* if a Dacl is present write Dacl out */
466    /* if we have SeRestorePrivilege, write owner and group info out */
467
468    if(sdc & SE_DACL_PRESENT) {
469        dwDesiredAccess |= WRITE_DAC;
470        RequestedInfo |= DACL_SECURITY_INFORMATION;
471
472        if(bRestorePrivilege) {
473            dwDesiredAccess |= WRITE_OWNER;
474            RequestedInfo |= (OWNER_SECURITY_INFORMATION |
475              GROUP_SECURITY_INFORMATION);
476        }
477    }
478
479    /* if a Sacl is present and we have either SeRestorePrivilege or
480       SeSystemSecurityPrivilege try to write Sacl out */
481
482    if((sdc & SE_SACL_PRESENT) && (bRestorePrivilege || bSaclPrivilege)) {
483        dwDesiredAccess |= ACCESS_SYSTEM_SECURITY;
484        RequestedInfo |= SACL_SECURITY_INFORMATION;
485    }
486
487    if(RequestedInfo == 0)  /* nothing to do */
488        return FALSE;
489
490    if(bRestorePrivilege)
491        dwFlags |= FILE_FLAG_BACKUP_SEMANTICS;
492
493    hFile = CreateFileA(
494        resource,
495        dwDesiredAccess,
496        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,/* max sharing */
497        NULL,
498        OPEN_EXISTING,
499        dwFlags,
500        NULL
501        );
502
503    if(hFile == INVALID_HANDLE_VALUE)
504        return FALSE;
505
506    bSuccess = SetKernelObjectSecurity(hFile, RequestedInfo, sd);
507
508    CloseHandle(hFile);
509
510    return bSuccess;
511}
512
513static VOID InitLocalPrivileges(VOID)
514{
515    HANDLE hToken;
516    TOKEN_PRIVILEGES tp;
517
518    /* try to enable some interesting privileges that give us the ability
519       to get some security information that we normally cannot.
520
521       note that enabling privileges is only relevant on the local machine;
522       when accessing files that are on a remote machine, any privileges
523       that are present on the remote machine get enabled by default. */
524
525    if(!OpenProcessToken(GetCurrentProcess(),
526        TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken))
527        return;
528
529    tp.PrivilegeCount = 1;
530    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
531
532    if(LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &tp.Privileges[0].Luid)) {
533
534        /* try to enable SeRestorePrivilege; if this succeeds, we can write
535           all aspects of the security descriptor */
536
537        if(AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL) &&
538           GetLastError() == ERROR_SUCCESS) g_bRestorePrivilege = TRUE;
539
540    }
541
542    /* try to enable SeSystemSecurityPrivilege, if SeRestorePrivilege not
543       present; if this succeeds, we can write the Sacl */
544
545    if(!g_bRestorePrivilege &&
546        LookupPrivilegeValue(NULL, SE_SECURITY_NAME, &tp.Privileges[0].Luid)) {
547
548        if(AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL) &&
549           GetLastError() == ERROR_SUCCESS) g_bSaclPrivilege = TRUE;
550    }
551
552    CloseHandle(hToken);
553}
554#endif /* NTSD_EAS */
555