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.
@@ -0,0 +1,410 @@
1
+ """
2
+ Windows registry utilities for managing security zones and network settings.
3
+
4
+ This module provides functions for working with the Windows registry,
5
+ particularly for managing security zones for UNC paths, which can help
6
+ resolve access issues with network shares.
7
+ """
8
+
9
+ import os
10
+ import re
11
+ import logging
12
+ from typing import Optional, Dict, List, Tuple, Any, Union
13
+
14
+ # Set up module-level logger
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # Check if we're running on Windows
18
+ IS_WINDOWS = os.name == 'nt'
19
+
20
+ # Try to import Windows-specific modules
21
+ if IS_WINDOWS:
22
+ try:
23
+ import winreg
24
+ HAVE_WINREG = True
25
+ except ImportError:
26
+ HAVE_WINREG = False
27
+ logger.warning("winreg module not available. Registry operations will not work.")
28
+ else:
29
+ HAVE_WINREG = False
30
+
31
+ # Constants for Windows security zones
32
+ ZONE_LOCAL_INTRANET = 1
33
+ ZONE_TRUSTED_SITES = 2
34
+ ZONE_INTERNET = 3
35
+ ZONE_RESTRICTED_SITES = 4
36
+
37
+ # Registry paths
38
+ ZONEMAP_KEY_PATH = r"Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap"
39
+ DOMAINS_KEY_PATH = ZONEMAP_KEY_PATH + r"\Domains"
40
+ RANGES_KEY_PATH = ZONEMAP_KEY_PATH + r"\Ranges"
41
+
42
+ def _ensure_admin_access() -> bool:
43
+ """
44
+ Check if the script has administrative access to the Windows registry.
45
+
46
+ Returns:
47
+ True if the script has administrative access, False otherwise.
48
+ """
49
+ if not IS_WINDOWS:
50
+ return False
51
+
52
+ try:
53
+ import ctypes
54
+ return bool(ctypes.windll.shell32.IsUserAnAdmin())
55
+ except:
56
+ # If we can't check, assume we don't have admin access
57
+ return False
58
+
59
+ def add_to_intranet_zone(server_name: str, for_all_users: bool = False) -> bool:
60
+ """
61
+ Add a server to the Local Intranet security zone.
62
+
63
+ This can help resolve access issues with UNC paths by lowering the security
64
+ restrictions for the specified server.
65
+
66
+ Args:
67
+ server_name: The name of the server to add to the zone.
68
+ for_all_users: If True, attempt to add the server to the zone for all users.
69
+ This requires administrative privileges.
70
+
71
+ Returns:
72
+ True if the server was successfully added to the zone, False otherwise.
73
+ """
74
+ if not IS_WINDOWS or not HAVE_WINREG:
75
+ logger.warning("Registry operations are only available on Windows.")
76
+ return False
77
+
78
+ # Validate server name
79
+ if not re.match(r'^[a-zA-Z0-9][a-zA-Z0-9\-\.]*$', server_name):
80
+ logger.error(f"Invalid server name: {server_name}")
81
+ return False
82
+
83
+ # Check if for_all_users requires admin access
84
+ if for_all_users and not _ensure_admin_access():
85
+ logger.warning("Administrative access required to add zone for all users.")
86
+ for_all_users = False
87
+
88
+ # Determine which registry hive to use
89
+ if for_all_users:
90
+ root_key = winreg.HKEY_LOCAL_MACHINE
91
+ logger.info(f"Adding {server_name} to Local Intranet zone for all users")
92
+ else:
93
+ root_key = winreg.HKEY_CURRENT_USER
94
+ logger.info(f"Adding {server_name} to Local Intranet zone for current user")
95
+
96
+ try:
97
+ # Create or open the domain key
98
+ domain_path = DOMAINS_KEY_PATH + "\\" + server_name
99
+
100
+ try:
101
+ # Try to create the key
102
+ domain_key = winreg.CreateKeyEx(root_key, domain_path, 0, winreg.KEY_WRITE)
103
+ except PermissionError:
104
+ logger.error(f"Permission denied accessing registry key: {domain_path}")
105
+ return False
106
+
107
+ try:
108
+ # Set the "*" value to Local Intranet zone (1)
109
+ winreg.SetValueEx(domain_key, "*", 0, winreg.REG_DWORD, ZONE_LOCAL_INTRANET)
110
+ logger.info(f"Added {server_name} to Local Intranet zone successfully.")
111
+ success = True
112
+ except Exception as e:
113
+ logger.error(f"Failed to add {server_name} to zone: {e}")
114
+ success = False
115
+
116
+ # Close the key
117
+ winreg.CloseKey(domain_key)
118
+
119
+ return success
120
+ except Exception as e:
121
+ logger.error(f"Failed to add {server_name} to Local Intranet zone: {e}")
122
+ return False
123
+
124
+ def remove_from_zone(server_name: str, for_all_users: bool = False) -> bool:
125
+ """
126
+ Remove a server from any security zone.
127
+
128
+ Args:
129
+ server_name: The name of the server to remove from zones.
130
+ for_all_users: If True, attempt to remove the server from zones for all users.
131
+ This requires administrative privileges.
132
+
133
+ Returns:
134
+ True if the server was successfully removed, False otherwise.
135
+ """
136
+ if not IS_WINDOWS or not HAVE_WINREG:
137
+ logger.warning("Registry operations are only available on Windows.")
138
+ return False
139
+
140
+ # Check if for_all_users requires admin access
141
+ if for_all_users and not _ensure_admin_access():
142
+ logger.warning("Administrative access required to remove zone for all users.")
143
+ for_all_users = False
144
+
145
+ # Determine which registry hive to use
146
+ if for_all_users:
147
+ root_key = winreg.HKEY_LOCAL_MACHINE
148
+ logger.info(f"Removing {server_name} from security zones for all users")
149
+ else:
150
+ root_key = winreg.HKEY_CURRENT_USER
151
+ logger.info(f"Removing {server_name} from security zones for current user")
152
+
153
+ try:
154
+ # Check if the key exists
155
+ domain_path = DOMAINS_KEY_PATH + "\\" + server_name
156
+
157
+ try:
158
+ # Try to open the key
159
+ try:
160
+ domain_key = winreg.OpenKey(root_key, domain_path, 0, winreg.KEY_READ)
161
+ # Close right away, we just want to see if it exists
162
+ winreg.CloseKey(domain_key)
163
+ except FileNotFoundError:
164
+ logger.info(f"Server {server_name} not found in any zone.")
165
+ return True # Already not in a zone
166
+
167
+ # Delete the key
168
+ try:
169
+ winreg.DeleteKey(root_key, domain_path)
170
+ logger.info(f"Removed {server_name} from security zones successfully.")
171
+ return True
172
+ except Exception as e:
173
+ logger.error(f"Failed to remove {server_name} from zones: {e}")
174
+ return False
175
+ except PermissionError:
176
+ logger.error(f"Permission denied accessing registry key: {domain_path}")
177
+ return False
178
+ except Exception as e:
179
+ logger.error(f"Failed to remove {server_name} from zones: {e}")
180
+ return False
181
+
182
+ def check_zone(server_name: str) -> Optional[int]:
183
+ """
184
+ Check which security zone a server is in.
185
+
186
+ Args:
187
+ server_name: The name of the server to check.
188
+
189
+ Returns:
190
+ The zone number if found, or None if the server is not in any zone.
191
+ Zone numbers:
192
+ 1: Local Intranet
193
+ 2: Trusted Sites
194
+ 3: Internet
195
+ 4: Restricted Sites
196
+ """
197
+ if not IS_WINDOWS or not HAVE_WINREG:
198
+ logger.warning("Registry operations are only available on Windows.")
199
+ return None
200
+
201
+ try:
202
+ # Check current user settings first
203
+ domain_path = DOMAINS_KEY_PATH + "\\" + server_name
204
+
205
+ try:
206
+ # Try to open the key
207
+ with winreg.OpenKey(winreg.HKEY_CURRENT_USER, domain_path, 0, winreg.KEY_READ) as domain_key:
208
+ try:
209
+ value, _ = winreg.QueryValueEx(domain_key, "*")
210
+ return value
211
+ except FileNotFoundError:
212
+ # Check if there are any other entries
213
+ i = 0
214
+ while True:
215
+ try:
216
+ name, value, _ = winreg.EnumValue(domain_key, i)
217
+ if name and value is not None:
218
+ return value
219
+ i += 1
220
+ except OSError:
221
+ break
222
+ except FileNotFoundError:
223
+ pass
224
+
225
+ # If not found in current user, check all users (HKLM)
226
+ try:
227
+ with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, domain_path, 0, winreg.KEY_READ) as domain_key:
228
+ try:
229
+ value, _ = winreg.QueryValueEx(domain_key, "*")
230
+ return value
231
+ except FileNotFoundError:
232
+ # Check if there are any other entries
233
+ i = 0
234
+ while True:
235
+ try:
236
+ name, value, _ = winreg.EnumValue(domain_key, i)
237
+ if name and value is not None:
238
+ return value
239
+ i += 1
240
+ except OSError:
241
+ break
242
+ except (FileNotFoundError, PermissionError):
243
+ pass
244
+
245
+ # Not found in any zone
246
+ return None
247
+ except Exception as e:
248
+ logger.error(f"Failed to check zone for {server_name}: {e}")
249
+ return None
250
+
251
+ def fix_security_zone(server_name: str) -> bool:
252
+ """
253
+ Fix security zone issues for a server.
254
+
255
+ This function adds the server to the Local Intranet zone, which can help
256
+ resolve access issues with UNC paths.
257
+
258
+ Args:
259
+ server_name: The name of the server to fix.
260
+
261
+ Returns:
262
+ True if the fix was applied successfully, False otherwise.
263
+ """
264
+ # Check if the server is already in a zone
265
+ current_zone = check_zone(server_name)
266
+
267
+ if current_zone == ZONE_LOCAL_INTRANET:
268
+ logger.info(f"Server {server_name} is already in the Local Intranet zone.")
269
+ return True
270
+
271
+ # Try to add to current user's zones first
272
+ if add_to_intranet_zone(server_name, for_all_users=False):
273
+ return True
274
+
275
+ # If that fails and we have admin access, try for all users
276
+ if _ensure_admin_access():
277
+ return add_to_intranet_zone(server_name, for_all_users=True)
278
+
279
+ return False
280
+
281
+ def get_all_zone_servers() -> Dict[str, int]:
282
+ """
283
+ Get all servers that are in security zones.
284
+
285
+ Returns:
286
+ A dictionary mapping server names to zone numbers.
287
+ """
288
+ if not IS_WINDOWS or not HAVE_WINREG:
289
+ logger.warning("Registry operations are only available on Windows.")
290
+ return {}
291
+
292
+ servers = {}
293
+
294
+ try:
295
+ # Check HKCU first
296
+ try:
297
+ with winreg.OpenKey(winreg.HKEY_CURRENT_USER, DOMAINS_KEY_PATH, 0, winreg.KEY_READ) as domains_key:
298
+ i = 0
299
+ while True:
300
+ try:
301
+ server = winreg.EnumKey(domains_key, i)
302
+ servers[server] = check_zone(server)
303
+ i += 1
304
+ except OSError:
305
+ break
306
+ except (FileNotFoundError, PermissionError):
307
+ pass
308
+
309
+ # Then check HKLM if we have access
310
+ try:
311
+ with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, DOMAINS_KEY_PATH, 0, winreg.KEY_READ) as domains_key:
312
+ i = 0
313
+ while True:
314
+ try:
315
+ server = winreg.EnumKey(domains_key, i)
316
+ if server not in servers: # Only add if not already found in HKCU
317
+ servers[server] = check_zone(server)
318
+ i += 1
319
+ except OSError:
320
+ break
321
+ except (FileNotFoundError, PermissionError):
322
+ pass
323
+
324
+ return servers
325
+ except Exception as e:
326
+ logger.error(f"Failed to get all zone servers: {e}")
327
+ return {}
328
+
329
+ def backup_zone_settings(backup_file: str) -> bool:
330
+ """
331
+ Backup all security zone settings to a file.
332
+
333
+ Args:
334
+ backup_file: The path to save the backup to.
335
+
336
+ Returns:
337
+ True if the backup was successful, False otherwise.
338
+ """
339
+ import json
340
+
341
+ servers = get_all_zone_servers()
342
+
343
+ if not servers:
344
+ logger.warning("No zone settings found to backup.")
345
+ return False
346
+
347
+ try:
348
+ with open(backup_file, 'w') as f:
349
+ json.dump(servers, f, indent=2)
350
+ logger.info(f"Backed up zone settings for {len(servers)} servers to {backup_file}")
351
+ return True
352
+ except Exception as e:
353
+ logger.error(f"Failed to backup zone settings: {e}")
354
+ return False
355
+
356
+ def restore_zone_settings(backup_file: str, for_all_users: bool = False) -> Tuple[int, int]:
357
+ """
358
+ Restore security zone settings from a backup file.
359
+
360
+ Args:
361
+ backup_file: The path to the backup file.
362
+ for_all_users: If True, attempt to restore settings for all users.
363
+ This requires administrative privileges.
364
+
365
+ Returns:
366
+ A tuple of (success_count, failure_count) indicating how many servers
367
+ were successfully restored and how many failed.
368
+ """
369
+ import json
370
+
371
+ try:
372
+ with open(backup_file, 'r') as f:
373
+ servers = json.load(f)
374
+ except Exception as e:
375
+ logger.error(f"Failed to read backup file {backup_file}: {e}")
376
+ return (0, 0)
377
+
378
+ success_count = 0
379
+ failure_count = 0
380
+
381
+ for server, zone in servers.items():
382
+ try:
383
+ # Remove from all zones first
384
+ remove_from_zone(server)
385
+
386
+ if zone == ZONE_LOCAL_INTRANET:
387
+ if add_to_intranet_zone(server, for_all_users=for_all_users):
388
+ success_count += 1
389
+ else:
390
+ failure_count += 1
391
+ elif zone == ZONE_TRUSTED_SITES:
392
+ # Add code to restore to trusted sites
393
+ # Currently not implemented
394
+ failure_count += 1
395
+ elif zone == ZONE_INTERNET:
396
+ # Add code to restore to internet zone
397
+ # Currently not implemented
398
+ failure_count += 1
399
+ elif zone == ZONE_RESTRICTED_SITES:
400
+ # Add code to restore to restricted sites
401
+ # Currently not implemented
402
+ failure_count += 1
403
+ else:
404
+ failure_count += 1
405
+ except Exception as e:
406
+ logger.error(f"Failed to restore zone for {server}: {e}")
407
+ failure_count += 1
408
+
409
+ logger.info(f"Restored zone settings: {success_count} succeeded, {failure_count} failed")
410
+ return (success_count, failure_count)