1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
#!/usr/bin/env python3
# Periodic job to update LDAP storageAvailable[/] from Windows PCs.
# > pip install keyring ldap3
import ctypes
import enum
import keyring
import ldap3
from ldap3.utils.conv import escape_filter_chars as escape_filter
from pprint import pprint
from socket import gethostname
import ssl
import sys
BYTES_PER_kB = 1024
BYTES_PER_MB = 1024 * 1024
MAX_PATH = 255
def GetLogicalDriveStrings():
import ctypes
import ctypes.wintypes
_GetLogicalDriveStrings = ctypes.windll.kernel32.GetLogicalDriveStringsW
buffer_len = 16 * 1024
#out_buffer = ctypes.create_unicode_buffer(buffer_len + 1)
out_buffer = ctypes.create_string_buffer(buffer_len*2 + 2)
ret = _GetLogicalDriveStrings(ctypes.wintypes.DWORD(buffer_len),
out_buffer)
if ret:
return out_buffer.raw.decode("utf-16").strip("\0").split("\0")
else:
raise ctypes.WinError()
class DriveType(enum.IntEnum):
Unknown = 0
NoRootDir = 1
Removable = 2
Fixed = 3
Remote = 4
CdRom = 5
RamDisk = 6
def GetDriveType(path):
import ctypes
import ctypes.wintypes
_GetDriveType = ctypes.windll.kernel32.GetDriveTypeW
ret = _GetDriveType(ctypes.wintypes.LPCWSTR(path))
return DriveType(ret)
def GetDiskFreeSpaceEx(path):
import ctypes
import ctypes.wintypes
_GetDiskFreeSpaceEx = ctypes.windll.kernel32.GetDiskFreeSpaceExW
out_avail_bytes = ctypes.wintypes.ULARGE_INTEGER()
out_total_bytes = ctypes.wintypes.ULARGE_INTEGER()
out_free_bytes = ctypes.wintypes.ULARGE_INTEGER()
ret = _GetDiskFreeSpaceEx(ctypes.wintypes.LPCWSTR(path),
ctypes.byref(out_avail_bytes),
ctypes.byref(out_total_bytes),
ctypes.byref(out_free_bytes))
if ret:
return (out_avail_bytes.value,
out_total_bytes.value,
out_free_bytes.value)
else:
raise ctypes.WinError()
def GetVolumeInformation(path):
import ctypes
import ctypes.wintypes
_GetVolumeInformation = ctypes.windll.kernel32.GetVolumeInformationW
out_volume_name = ctypes.create_unicode_buffer(MAX_PATH + 1)
out_serial_number = ctypes.wintypes.DWORD()
out_max_component_len = ctypes.wintypes.DWORD()
out_flags = ctypes.wintypes.DWORD()
out_fs_name = ctypes.create_unicode_buffer(MAX_PATH + 1)
ret = _GetVolumeInformation(ctypes.wintypes.LPCWSTR(path),
out_volume_name,
ctypes.sizeof(out_volume_name),
ctypes.byref(out_serial_number),
ctypes.byref(out_max_component_len),
ctypes.byref(out_flags),
out_fs_name,
ctypes.sizeof(out_fs_name))
if ret:
return (out_volume_name.value,
out_serial_number.value,
out_max_component_len.value,
out_flags.value,
out_fs_name.value)
else:
raise ctypes.WinError()
def find_bind_credentials():
server = "ldap/ldap.nullroute.lt"
username = gethostname().lower()
cred = keyring.get_credential(server, username) or \
keyring.get_credential(server, None)
if not cred:
print("Error: bind password not found in keyring. You need to run:", file=sys.stderr)
print(" > python -m keyring set \"%s\" \"%s\"" % (server, username), file=sys.stderr)
exit(1)
return cred.username, cred.password
def ldap_connect():
username, password = find_bind_credentials()
serv = ldap3.Server("ldaps://ldap.nullroute.lt",
tls=ldap3.Tls(validate=ssl.CERT_REQUIRED),
get_info=ldap3.DSA)
bind_dn = username
if "=" not in bind_dn:
conn = ldap3.Connection(serv,
raise_exceptions=True)
conn.bind()
bind_dn = ldap_find_device_dn(conn,
"(cn=%s)" % escape_filter(username))
if not bind_dn:
raise Exception("Could not find the bind DN for %r" % username)
conn = ldap3.Connection(serv,
user=bind_dn,
password=password,
raise_exceptions=True)
conn.bind()
return conn
def ldap_find_device_dn(conn, filter):
conn.search("o=Nullroute",
"(&(objectClass=device)(|%s))" % filter,
attributes=["1.1"])
if len(conn.response) == 1:
return conn.response[0]["dn"]
elif len(conn.response) > 1:
print("More than one device found for %s" % filter, file=sys.stderr)
else:
# Not necessarily an error case, just an untracked volume
print("No devices found for %s" % filter)
def ldap_update_device(conn, device_dn, total_mb, free_mb):
conn.modify(device_dn,
{"storageCapacity": [(ldap3.MODIFY_REPLACE, [str(total_mb)])],
"storageAvailable": [(ldap3.MODIFY_REPLACE, [str(free_mb)])]})
conn = ldap_connect()
print("Bound as", conn.extend.standard.who_am_i())
if sys.platform != "win32":
exit(f"error: Platform {sys.platform!r} not supported by this script")
unc_hostname = gethostname().lower()
for vol_path in GetLogicalDriveStrings():
if GetDriveType(vol_path) != DriveType.Fixed:
continue
try:
(avail_bytes, total_bytes, free_bytes) = GetDiskFreeSpaceEx(vol_path)
total_mb = total_bytes // BYTES_PER_MB
free_mb = free_bytes // BYTES_PER_MB
# Reduce precision to reduce LDAP updates
total_mb -= total_mb % 100
free_mb -= free_mb % 100
print("Volume %s has ~%s MiB free out of ~%s MiB" % (vol_path, free_mb, total_mb))
except PermissionError as e:
# PermissionError: [WinError 21] The device is not ready.
continue
(label, serial, *rest) = GetVolumeInformation(vol_path)
serial = "%04X-%04X" % ((serial >> 16) & 0xFFFF, serial & 0xFFFF)
unc = r"\\%s\%s" % (unc_hostname, vol_path.rstrip("\\"))
filter = "(storageUniqueIdentifier=%s)" % escape_filter(serial)
filter += "(cn=%s)" % escape_filter(unc)
device_dn = ldap_find_device_dn(conn, filter)
if device_dn:
print("Updating", device_dn)
ldap_update_device(conn, device_dn, total_mb, free_mb)
|