unctools 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unctools/__init__.py +100 -0
- unctools/converter.py +378 -0
- unctools/detector.py +531 -0
- unctools/operations.py +562 -0
- unctools/utils/__init__.py +40 -0
- unctools/utils/compat.py +331 -0
- unctools/utils/logger.py +228 -0
- unctools/utils/validation.py +321 -0
- unctools/windows/__init__.py +45 -0
- unctools/windows/network.py +490 -0
- unctools/windows/registry.py +410 -0
- unctools/windows/security.py +586 -0
- unctools-0.1.0.dist-info/METADATA +189 -0
- unctools-0.1.0.dist-info/RECORD +17 -0
- unctools-0.1.0.dist-info/WHEEL +5 -0
- unctools-0.1.0.dist-info/licenses/LICENSE +21 -0
- unctools-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Windows security utilities for UNC paths and network drives.
|
|
3
|
+
|
|
4
|
+
This module provides functions for handling Windows security aspects of UNC paths,
|
|
5
|
+
including access permissions, sharing options, and security token management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import logging
|
|
10
|
+
import subprocess
|
|
11
|
+
from typing import Dict, List, Optional, Tuple, Union, Any
|
|
12
|
+
|
|
13
|
+
# Set up module-level logger
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# Check if we're running on Windows
|
|
17
|
+
IS_WINDOWS = os.name == 'nt'
|
|
18
|
+
|
|
19
|
+
# Try to import Windows-specific modules
|
|
20
|
+
if IS_WINDOWS:
|
|
21
|
+
try:
|
|
22
|
+
import win32security
|
|
23
|
+
import win32api
|
|
24
|
+
import win32con
|
|
25
|
+
import win32net
|
|
26
|
+
import ntsecuritycon
|
|
27
|
+
HAVE_WIN32SECURITY = True
|
|
28
|
+
except ImportError:
|
|
29
|
+
HAVE_WIN32SECURITY = False
|
|
30
|
+
logger.warning("win32security module not available. Security operations will use limited functionality.")
|
|
31
|
+
else:
|
|
32
|
+
HAVE_WIN32SECURITY = False
|
|
33
|
+
|
|
34
|
+
# Define or import constants for ACE types
|
|
35
|
+
if IS_WINDOWS and HAVE_WIN32SECURITY:
|
|
36
|
+
# Use constants from win32security when available
|
|
37
|
+
ACCESS_ALLOWED_ACE_TYPE = win32security.ACCESS_ALLOWED_ACE_TYPE
|
|
38
|
+
ACCESS_DENIED_ACE_TYPE = win32security.ACCESS_DENIED_ACE_TYPE
|
|
39
|
+
SYSTEM_AUDIT_ACE_TYPE = win32security.SYSTEM_AUDIT_ACE_TYPE
|
|
40
|
+
|
|
41
|
+
# SYSTEM_ALARM_ACE_TYPE may not be available in all versions
|
|
42
|
+
try:
|
|
43
|
+
SYSTEM_ALARM_ACE_TYPE = win32security.SYSTEM_ALARM_ACE_TYPE
|
|
44
|
+
except AttributeError:
|
|
45
|
+
# Define fallback value based on Windows SDK
|
|
46
|
+
SYSTEM_ALARM_ACE_TYPE = 3
|
|
47
|
+
logger.debug("win32security.SYSTEM_ALARM_ACE_TYPE not available, using fallback value")
|
|
48
|
+
else:
|
|
49
|
+
# Define fallback values for non-Windows or without win32security
|
|
50
|
+
ACCESS_ALLOWED_ACE_TYPE = 0
|
|
51
|
+
ACCESS_DENIED_ACE_TYPE = 1
|
|
52
|
+
SYSTEM_AUDIT_ACE_TYPE = 2
|
|
53
|
+
SYSTEM_ALARM_ACE_TYPE = 3
|
|
54
|
+
|
|
55
|
+
def get_file_security(path: str) -> Optional[Dict[str, Any]]:
|
|
56
|
+
"""
|
|
57
|
+
Get security information for a file or directory.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
path: The path to get security information for.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
A dictionary with security information, or None if not available.
|
|
64
|
+
"""
|
|
65
|
+
if not IS_WINDOWS or not HAVE_WIN32SECURITY:
|
|
66
|
+
logger.warning("Security information is only available on Windows with win32security.")
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
# Get security descriptor
|
|
71
|
+
sd = win32security.GetFileSecurity(
|
|
72
|
+
path,
|
|
73
|
+
win32security.OWNER_SECURITY_INFORMATION |
|
|
74
|
+
win32security.GROUP_SECURITY_INFORMATION |
|
|
75
|
+
win32security.DACL_SECURITY_INFORMATION
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Get owner, group, and DACL information
|
|
79
|
+
owner_sid = sd.GetSecurityDescriptorOwner()
|
|
80
|
+
group_sid = sd.GetSecurityDescriptorGroup()
|
|
81
|
+
dacl = sd.GetSecurityDescriptorDacl()
|
|
82
|
+
|
|
83
|
+
# Convert SIDs to names
|
|
84
|
+
try:
|
|
85
|
+
owner_name, domain, _ = win32security.LookupAccountSid(None, owner_sid)
|
|
86
|
+
owner = f"{domain}\\{owner_name}"
|
|
87
|
+
except:
|
|
88
|
+
owner = str(owner_sid)
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
group_name, domain, _ = win32security.LookupAccountSid(None, group_sid)
|
|
92
|
+
group = f"{domain}\\{group_name}"
|
|
93
|
+
except:
|
|
94
|
+
group = str(group_sid)
|
|
95
|
+
|
|
96
|
+
# Parse DACL
|
|
97
|
+
acl_entries = []
|
|
98
|
+
if dacl:
|
|
99
|
+
for i in range(dacl.GetAceCount()):
|
|
100
|
+
ace = dacl.GetAce(i)
|
|
101
|
+
try:
|
|
102
|
+
trustee_sid = ace[2]
|
|
103
|
+
trustee_name, domain, _ = win32security.LookupAccountSid(None, trustee_sid)
|
|
104
|
+
trustee = f"{domain}\\{trustee_name}"
|
|
105
|
+
except:
|
|
106
|
+
trustee = str(trustee_sid)
|
|
107
|
+
|
|
108
|
+
# Get access mask and type
|
|
109
|
+
ace_type = ace[0][0] # Type
|
|
110
|
+
ace_flags = ace[0][1] # Flags
|
|
111
|
+
ace_mask = ace[1] # Access mask
|
|
112
|
+
|
|
113
|
+
acl_entries.append({
|
|
114
|
+
'trustee': trustee,
|
|
115
|
+
'type': _get_ace_type_name(ace_type),
|
|
116
|
+
'flags': _get_ace_flags(ace_flags),
|
|
117
|
+
'permissions': _get_permission_names(ace_mask)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
'owner': owner,
|
|
122
|
+
'group': group,
|
|
123
|
+
'acl': acl_entries
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.error(f"Failed to get security information for {path}: {e}")
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
def _get_ace_type_name(ace_type: int) -> str:
|
|
131
|
+
"""
|
|
132
|
+
Get a string representation of the ACE type.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
ace_type: The ACE type value.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
A string describing the ACE type.
|
|
139
|
+
"""
|
|
140
|
+
ace_types = {
|
|
141
|
+
ACCESS_ALLOWED_ACE_TYPE: "Allow",
|
|
142
|
+
ACCESS_DENIED_ACE_TYPE: "Deny",
|
|
143
|
+
SYSTEM_AUDIT_ACE_TYPE: "Audit",
|
|
144
|
+
SYSTEM_ALARM_ACE_TYPE: "Alarm"
|
|
145
|
+
}
|
|
146
|
+
return ace_types.get(ace_type, f"Unknown ({ace_type})")
|
|
147
|
+
|
|
148
|
+
def _get_ace_flags(ace_flags: int) -> List[str]:
|
|
149
|
+
"""
|
|
150
|
+
Get a list of string representations of the ACE flags.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
ace_flags: The ACE flags value.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
A list of strings describing the ACE flags.
|
|
157
|
+
"""
|
|
158
|
+
flags = []
|
|
159
|
+
if IS_WINDOWS and HAVE_WIN32SECURITY:
|
|
160
|
+
if ace_flags & win32security.OBJECT_INHERIT_ACE:
|
|
161
|
+
flags.append("Object Inherit")
|
|
162
|
+
if ace_flags & win32security.CONTAINER_INHERIT_ACE:
|
|
163
|
+
flags.append("Container Inherit")
|
|
164
|
+
if ace_flags & win32security.NO_PROPAGATE_INHERIT_ACE:
|
|
165
|
+
flags.append("No Propagate")
|
|
166
|
+
if ace_flags & win32security.INHERIT_ONLY_ACE:
|
|
167
|
+
flags.append("Inherit Only")
|
|
168
|
+
if ace_flags & win32security.INHERITED_ACE:
|
|
169
|
+
flags.append("Inherited")
|
|
170
|
+
return flags
|
|
171
|
+
|
|
172
|
+
def _get_permission_names(access_mask: int) -> List[str]:
|
|
173
|
+
"""
|
|
174
|
+
Get a list of string representations of the permissions.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
access_mask: The access mask value.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
A list of strings describing the permissions.
|
|
181
|
+
"""
|
|
182
|
+
permissions = []
|
|
183
|
+
|
|
184
|
+
# Only try to decode permissions if we have the necessary module
|
|
185
|
+
if IS_WINDOWS and HAVE_WIN32SECURITY:
|
|
186
|
+
# File permissions
|
|
187
|
+
if access_mask & ntsecuritycon.FILE_READ_DATA:
|
|
188
|
+
permissions.append("Read")
|
|
189
|
+
if access_mask & ntsecuritycon.FILE_WRITE_DATA:
|
|
190
|
+
permissions.append("Write")
|
|
191
|
+
if access_mask & ntsecuritycon.FILE_APPEND_DATA:
|
|
192
|
+
permissions.append("Append")
|
|
193
|
+
if access_mask & ntsecuritycon.FILE_EXECUTE:
|
|
194
|
+
permissions.append("Execute")
|
|
195
|
+
if access_mask & ntsecuritycon.DELETE:
|
|
196
|
+
permissions.append("Delete")
|
|
197
|
+
if access_mask & ntsecuritycon.READ_CONTROL:
|
|
198
|
+
permissions.append("Read Permissions")
|
|
199
|
+
if access_mask & ntsecuritycon.WRITE_DAC:
|
|
200
|
+
permissions.append("Change Permissions")
|
|
201
|
+
if access_mask & ntsecuritycon.WRITE_OWNER:
|
|
202
|
+
permissions.append("Take Ownership")
|
|
203
|
+
|
|
204
|
+
# Generic permissions
|
|
205
|
+
if access_mask & ntsecuritycon.GENERIC_READ:
|
|
206
|
+
permissions.append("Generic Read")
|
|
207
|
+
if access_mask & ntsecuritycon.GENERIC_WRITE:
|
|
208
|
+
permissions.append("Generic Write")
|
|
209
|
+
if access_mask & ntsecuritycon.GENERIC_EXECUTE:
|
|
210
|
+
permissions.append("Generic Execute")
|
|
211
|
+
if access_mask & ntsecuritycon.GENERIC_ALL:
|
|
212
|
+
permissions.append("Full Control")
|
|
213
|
+
|
|
214
|
+
return permissions
|
|
215
|
+
|
|
216
|
+
def set_file_permissions(path: str, trustee: str, permissions: str,
|
|
217
|
+
allow: bool = True) -> bool:
|
|
218
|
+
"""
|
|
219
|
+
Set permissions on a file or directory.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
path: The path to set permissions on.
|
|
223
|
+
trustee: The user or group to set permissions for (e.g., "domain\\user").
|
|
224
|
+
permissions: The permission to set ("read", "write", "execute", "full").
|
|
225
|
+
allow: If True, grant the permissions; if False, deny them.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
True if successful, False otherwise.
|
|
229
|
+
"""
|
|
230
|
+
if not IS_WINDOWS or not HAVE_WIN32SECURITY:
|
|
231
|
+
logger.warning("Setting permissions is only available on Windows with win32security.")
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
# Get security descriptor
|
|
236
|
+
sd = win32security.GetFileSecurity(
|
|
237
|
+
path,
|
|
238
|
+
win32security.DACL_SECURITY_INFORMATION
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Get existing DACL
|
|
242
|
+
dacl = sd.GetSecurityDescriptorDacl()
|
|
243
|
+
if dacl is None:
|
|
244
|
+
dacl = win32security.ACL()
|
|
245
|
+
|
|
246
|
+
# Determine access mask based on permissions
|
|
247
|
+
access_mask = 0
|
|
248
|
+
if permissions.lower() == "read":
|
|
249
|
+
access_mask = ntsecuritycon.FILE_GENERIC_READ
|
|
250
|
+
elif permissions.lower() == "write":
|
|
251
|
+
access_mask = ntsecuritycon.FILE_GENERIC_WRITE
|
|
252
|
+
elif permissions.lower() == "execute":
|
|
253
|
+
access_mask = ntsecuritycon.FILE_GENERIC_EXECUTE
|
|
254
|
+
elif permissions.lower() == "full":
|
|
255
|
+
access_mask = ntsecuritycon.FILE_ALL_ACCESS
|
|
256
|
+
else:
|
|
257
|
+
logger.error(f"Unknown permission: {permissions}")
|
|
258
|
+
return False
|
|
259
|
+
|
|
260
|
+
# Convert trustee name to SID
|
|
261
|
+
try:
|
|
262
|
+
if "\\" in trustee:
|
|
263
|
+
domain, user = trustee.split("\\", 1)
|
|
264
|
+
else:
|
|
265
|
+
domain, user = "", trustee
|
|
266
|
+
|
|
267
|
+
trustee_sid, _, _ = win32security.LookupAccountName(None, trustee)
|
|
268
|
+
except Exception as e:
|
|
269
|
+
logger.error(f"Failed to look up SID for {trustee}: {e}")
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
# Add ACE to DACL
|
|
273
|
+
ace_type = ACCESS_ALLOWED_ACE_TYPE if allow else ACCESS_DENIED_ACE_TYPE
|
|
274
|
+
dacl.AddAccessAllowedAce(win32security.ACL_REVISION, access_mask, trustee_sid)
|
|
275
|
+
|
|
276
|
+
# Set new DACL
|
|
277
|
+
sd.SetSecurityDescriptorDacl(1, dacl, 0)
|
|
278
|
+
win32security.SetFileSecurity(
|
|
279
|
+
path,
|
|
280
|
+
win32security.DACL_SECURITY_INFORMATION,
|
|
281
|
+
sd
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
logger.info(f"Set {permissions} permissions for {trustee} on {path}")
|
|
285
|
+
return True
|
|
286
|
+
|
|
287
|
+
except Exception as e:
|
|
288
|
+
logger.error(f"Failed to set permissions on {path}: {e}")
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
def take_ownership(path: str) -> bool:
|
|
292
|
+
"""
|
|
293
|
+
Take ownership of a file or directory.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
path: The path to take ownership of.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
True if successful, False otherwise.
|
|
300
|
+
"""
|
|
301
|
+
if not IS_WINDOWS:
|
|
302
|
+
logger.warning("Taking ownership is only available on Windows.")
|
|
303
|
+
return False
|
|
304
|
+
|
|
305
|
+
# Try to use the Windows API if available
|
|
306
|
+
if HAVE_WIN32SECURITY:
|
|
307
|
+
try:
|
|
308
|
+
# Get current process token
|
|
309
|
+
token = win32security.OpenProcessToken(
|
|
310
|
+
win32api.GetCurrentProcess(),
|
|
311
|
+
win32security.TOKEN_ADJUST_PRIVILEGES | win32security.TOKEN_QUERY
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Enable SE_TAKE_OWNERSHIP_NAME privilege
|
|
315
|
+
privilege_id = win32security.LookupPrivilegeValue(
|
|
316
|
+
None, win32security.SE_TAKE_OWNERSHIP_NAME
|
|
317
|
+
)
|
|
318
|
+
win32security.AdjustTokenPrivileges(
|
|
319
|
+
token, 0, [(privilege_id, win32security.SE_PRIVILEGE_ENABLED)]
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Get current user SID
|
|
323
|
+
user_sid = win32security.GetTokenInformation(
|
|
324
|
+
token, win32security.TokenUser
|
|
325
|
+
)[0]
|
|
326
|
+
|
|
327
|
+
# Get security descriptor
|
|
328
|
+
sd = win32security.GetFileSecurity(
|
|
329
|
+
path, win32security.OWNER_SECURITY_INFORMATION
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Set new owner
|
|
333
|
+
sd.SetSecurityDescriptorOwner(user_sid, 0)
|
|
334
|
+
win32security.SetFileSecurity(
|
|
335
|
+
path, win32security.OWNER_SECURITY_INFORMATION, sd
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
logger.info(f"Successfully took ownership of {path}")
|
|
339
|
+
return True
|
|
340
|
+
|
|
341
|
+
except Exception as e:
|
|
342
|
+
logger.error(f"Failed to take ownership using API: {e}")
|
|
343
|
+
# Fall back to takeown command
|
|
344
|
+
|
|
345
|
+
# Fall back to using the takeown command
|
|
346
|
+
try:
|
|
347
|
+
result = subprocess.run(
|
|
348
|
+
['takeown', '/f', path],
|
|
349
|
+
text=True, capture_output=True, check=False
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
if result.returncode == 0:
|
|
353
|
+
logger.info(f"Successfully took ownership of {path} using takeown")
|
|
354
|
+
return True
|
|
355
|
+
else:
|
|
356
|
+
logger.error(f"Failed to take ownership using takeown: {result.stderr}")
|
|
357
|
+
return False
|
|
358
|
+
|
|
359
|
+
except Exception as e:
|
|
360
|
+
logger.error(f"Failed to take ownership: {e}")
|
|
361
|
+
return False
|
|
362
|
+
|
|
363
|
+
def check_access_rights(path: str, desired_access: str = "read") -> bool:
|
|
364
|
+
"""
|
|
365
|
+
Check if the current user has specific access rights to a path.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
path: The path to check.
|
|
369
|
+
desired_access: The type of access to check for ("read", "write", "execute", "full").
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
True if the user has the requested access, False otherwise.
|
|
373
|
+
"""
|
|
374
|
+
if not IS_WINDOWS:
|
|
375
|
+
# On non-Windows platforms, use simpler checks
|
|
376
|
+
if desired_access.lower() == "read":
|
|
377
|
+
return os.access(path, os.R_OK)
|
|
378
|
+
elif desired_access.lower() == "write":
|
|
379
|
+
return os.access(path, os.W_OK)
|
|
380
|
+
elif desired_access.lower() == "execute":
|
|
381
|
+
return os.access(path, os.X_OK)
|
|
382
|
+
elif desired_access.lower() == "full":
|
|
383
|
+
return os.access(path, os.R_OK | os.W_OK | os.X_OK)
|
|
384
|
+
else:
|
|
385
|
+
logger.error(f"Unknown access type: {desired_access}")
|
|
386
|
+
return False
|
|
387
|
+
|
|
388
|
+
# Windows-specific checks
|
|
389
|
+
if not HAVE_WIN32SECURITY:
|
|
390
|
+
logger.warning("Detailed access checks require win32security.")
|
|
391
|
+
# Fall back to simpler checks
|
|
392
|
+
try:
|
|
393
|
+
if desired_access.lower() == "read":
|
|
394
|
+
# Try to open for reading
|
|
395
|
+
with open(path, 'r'):
|
|
396
|
+
pass
|
|
397
|
+
return True
|
|
398
|
+
elif desired_access.lower() == "write":
|
|
399
|
+
# Check if file exists and is writable, or if directory exists and we can create a temp file
|
|
400
|
+
if os.path.isfile(path):
|
|
401
|
+
return os.access(path, os.W_OK)
|
|
402
|
+
elif os.path.isdir(path):
|
|
403
|
+
try:
|
|
404
|
+
temp_file = os.path.join(path, "temp_access_check")
|
|
405
|
+
with open(temp_file, 'w'):
|
|
406
|
+
pass
|
|
407
|
+
os.remove(temp_file)
|
|
408
|
+
return True
|
|
409
|
+
except:
|
|
410
|
+
return False
|
|
411
|
+
return False
|
|
412
|
+
elif desired_access.lower() in ("execute", "full"):
|
|
413
|
+
# These require more complex checks
|
|
414
|
+
logger.warning("Detailed execute/full access check not available without win32security.")
|
|
415
|
+
return False
|
|
416
|
+
else:
|
|
417
|
+
logger.error(f"Unknown access type: {desired_access}")
|
|
418
|
+
return False
|
|
419
|
+
except:
|
|
420
|
+
return False
|
|
421
|
+
|
|
422
|
+
# Use win32security for detailed checks
|
|
423
|
+
try:
|
|
424
|
+
# Map desired access to access mask
|
|
425
|
+
if desired_access.lower() == "read":
|
|
426
|
+
access_mask = ntsecuritycon.FILE_GENERIC_READ
|
|
427
|
+
elif desired_access.lower() == "write":
|
|
428
|
+
access_mask = ntsecuritycon.FILE_GENERIC_WRITE
|
|
429
|
+
elif desired_access.lower() == "execute":
|
|
430
|
+
access_mask = ntsecuritycon.FILE_GENERIC_EXECUTE
|
|
431
|
+
elif desired_access.lower() == "full":
|
|
432
|
+
access_mask = ntsecuritycon.FILE_ALL_ACCESS
|
|
433
|
+
else:
|
|
434
|
+
logger.error(f"Unknown access type: {desired_access}")
|
|
435
|
+
return False
|
|
436
|
+
|
|
437
|
+
# Check access
|
|
438
|
+
if os.path.isdir(path):
|
|
439
|
+
# For directories, we need to check directory-specific access
|
|
440
|
+
handle = win32security.CreateFile(
|
|
441
|
+
path,
|
|
442
|
+
access_mask,
|
|
443
|
+
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
|
|
444
|
+
None,
|
|
445
|
+
win32con.OPEN_EXISTING,
|
|
446
|
+
win32con.FILE_FLAG_BACKUP_SEMANTICS, # Required for directories
|
|
447
|
+
0
|
|
448
|
+
)
|
|
449
|
+
else:
|
|
450
|
+
# For files, use standard file access
|
|
451
|
+
handle = win32security.CreateFile(
|
|
452
|
+
path,
|
|
453
|
+
access_mask,
|
|
454
|
+
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
|
|
455
|
+
None,
|
|
456
|
+
win32con.OPEN_EXISTING,
|
|
457
|
+
0,
|
|
458
|
+
0
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
# If we got here, we have the requested access
|
|
462
|
+
win32api.CloseHandle(handle)
|
|
463
|
+
return True
|
|
464
|
+
|
|
465
|
+
except win32api.error as e:
|
|
466
|
+
# Access denied or other error
|
|
467
|
+
logger.debug(f"Access check failed for {path} with {desired_access}: {e}")
|
|
468
|
+
return False
|
|
469
|
+
except Exception as e:
|
|
470
|
+
logger.error(f"Error checking access rights: {e}")
|
|
471
|
+
return False
|
|
472
|
+
|
|
473
|
+
def get_unc_share_permissions(server: str, share: str) -> Optional[Dict[str, List[str]]]:
|
|
474
|
+
"""
|
|
475
|
+
Get permissions for a network share.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
server: The server name.
|
|
479
|
+
share: The share name.
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
A dictionary mapping users/groups to permission lists, or None if not available.
|
|
483
|
+
"""
|
|
484
|
+
if not IS_WINDOWS or not HAVE_WIN32SECURITY:
|
|
485
|
+
logger.warning("Share permissions are only available on Windows with win32security.")
|
|
486
|
+
return None
|
|
487
|
+
|
|
488
|
+
try:
|
|
489
|
+
# Get share information
|
|
490
|
+
server_unc = f"\\\\{server}"
|
|
491
|
+
share_info, _, _ = win32net.NetShareGetInfo(server, share, 502)
|
|
492
|
+
|
|
493
|
+
# Get share security descriptor
|
|
494
|
+
sd = share_info['security_descriptor']
|
|
495
|
+
if not sd:
|
|
496
|
+
logger.warning(f"No security descriptor available for {server}\\{share}")
|
|
497
|
+
return None
|
|
498
|
+
|
|
499
|
+
# Parse permissions
|
|
500
|
+
dacl = sd.GetSecurityDescriptorDacl()
|
|
501
|
+
if not dacl:
|
|
502
|
+
logger.warning(f"No DACL available for {server}\\{share}")
|
|
503
|
+
return None
|
|
504
|
+
|
|
505
|
+
permissions = {}
|
|
506
|
+
for i in range(dacl.GetAceCount()):
|
|
507
|
+
ace = dacl.GetAce(i)
|
|
508
|
+
try:
|
|
509
|
+
trustee_sid = ace[2]
|
|
510
|
+
trustee_name, domain, _ = win32security.LookupAccountSid(None, trustee_sid)
|
|
511
|
+
trustee = f"{domain}\\{trustee_name}"
|
|
512
|
+
except:
|
|
513
|
+
trustee = str(trustee_sid)
|
|
514
|
+
|
|
515
|
+
# Get access type and mask
|
|
516
|
+
ace_type = ace[0][0] # Type
|
|
517
|
+
ace_mask = ace[1] # Access mask
|
|
518
|
+
|
|
519
|
+
# Determine share permissions
|
|
520
|
+
perm_list = []
|
|
521
|
+
if ace_mask & win32netcon.ACCESS_READ:
|
|
522
|
+
perm_list.append("Read")
|
|
523
|
+
if ace_mask & win32netcon.ACCESS_WRITE:
|
|
524
|
+
perm_list.append("Write")
|
|
525
|
+
if ace_mask & win32netcon.ACCESS_CREATE:
|
|
526
|
+
perm_list.append("Create")
|
|
527
|
+
if ace_mask & win32netcon.ACCESS_EXEC:
|
|
528
|
+
perm_list.append("Execute")
|
|
529
|
+
if ace_mask & win32netcon.ACCESS_DELETE:
|
|
530
|
+
perm_list.append("Delete")
|
|
531
|
+
if ace_mask & win32netcon.ACCESS_ATRIB:
|
|
532
|
+
perm_list.append("Change Attributes")
|
|
533
|
+
if ace_mask & win32netcon.ACCESS_PERM:
|
|
534
|
+
perm_list.append("Change Permissions")
|
|
535
|
+
if ace_mask & win32netcon.ACCESS_ALL:
|
|
536
|
+
perm_list = ["Full Control"]
|
|
537
|
+
|
|
538
|
+
# Only include ALLOW aces (ignore DENY for now)
|
|
539
|
+
if ace_type == ACCESS_ALLOWED_ACE_TYPE:
|
|
540
|
+
permissions[trustee] = perm_list
|
|
541
|
+
|
|
542
|
+
return permissions
|
|
543
|
+
|
|
544
|
+
except Exception as e:
|
|
545
|
+
logger.error(f"Failed to get share permissions for {server}\\{share}: {e}")
|
|
546
|
+
return None
|
|
547
|
+
|
|
548
|
+
def bypass_security_dialog(enabled: bool = True) -> bool:
|
|
549
|
+
"""
|
|
550
|
+
Enable or disable the security warning dialog for UNC paths.
|
|
551
|
+
|
|
552
|
+
This modifies the registry to suppress security warnings when accessing UNC paths.
|
|
553
|
+
|
|
554
|
+
Args:
|
|
555
|
+
enabled: True to suppress warnings, False to restore default behavior.
|
|
556
|
+
|
|
557
|
+
Returns:
|
|
558
|
+
True if successful, False otherwise.
|
|
559
|
+
"""
|
|
560
|
+
if not IS_WINDOWS:
|
|
561
|
+
logger.warning("Registry modifications are only available on Windows.")
|
|
562
|
+
return False
|
|
563
|
+
|
|
564
|
+
try:
|
|
565
|
+
import winreg
|
|
566
|
+
|
|
567
|
+
# Registry key for UNC security
|
|
568
|
+
key_path = r"Software\Microsoft\Windows\CurrentVersion\Policies\Network"
|
|
569
|
+
value_name = "ClassicSharing"
|
|
570
|
+
|
|
571
|
+
# Open or create the key
|
|
572
|
+
try:
|
|
573
|
+
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_WRITE)
|
|
574
|
+
except:
|
|
575
|
+
key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, key_path)
|
|
576
|
+
|
|
577
|
+
# Set the value
|
|
578
|
+
winreg.SetValueEx(key, value_name, 0, winreg.REG_DWORD, 1 if enabled else 0)
|
|
579
|
+
winreg.CloseKey(key)
|
|
580
|
+
|
|
581
|
+
logger.info(f"{'Enabled' if enabled else 'Disabled'} UNC security bypass")
|
|
582
|
+
return True
|
|
583
|
+
|
|
584
|
+
except Exception as e:
|
|
585
|
+
logger.error(f"Failed to modify registry for UNC security bypass: {e}")
|
|
586
|
+
return False
|