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