forked from DaanSelen/meshbook
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmeshbook.py
More file actions
289 lines (234 loc) · 13 KB
/
Copy pathmeshbook.py
File metadata and controls
289 lines (234 loc) · 13 KB
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#!/bin/python3
# Public Python libraries
import argparse
import asyncio
from colorama import just_fix_windows_console
import pyotp
import json
import meshctrl
# Local Python libraries/modules
from modules.console import Console
from modules.executor import Executor
from modules.history import History
from modules.utilities import Transform, Utilities
meshbook_version = "1.3.2"
grace_period = 3 # Grace period will last for x (by default 3) second(s).
def define_cmdargs() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Process command-line arguments")
parser.add_argument("-mb", "--meshbook", type=str, help="Path to the meshbook yaml file.")
parser.add_argument("--historydir", type=str, help="Define a custom history log directory (default: ./history).", default="./history")
parser.add_argument("--nohistory", action="store_true", help="Disable the logging of the history into a local log (text) file inside './history'.")
parser.add_argument("--flushhistory", action="store_true", help="Clear old history logs before running the Meshbook.")
parser.add_argument("-oc", "--oscategories", type=str, help="Path to the Operating System categories JSON file.", default="./os_categories.json")
parser.add_argument("--conf", type=str, help="Path for the API configuration file (default: ./config.conf).", default="./api.conf")
parser.add_argument("--nograce", action="store_true", help="Disable the grace 3 seconds before running the meshbook.")
parser.add_argument("-g", "--group", type=str, help="Specify a manual override for the group.", default="")
parser.add_argument("-d", "--device", type=str, help="Specify a manual override for a device.", default="")
parser.add_argument("-i", "--indent", action="store_true", help="Use an JSON indentation of 4 when this flag is passed.", default=False)
parser.add_argument("-s", "--silent", action="store_true", help="Suppress terminal output.", default=False)
parser.add_argument("--shlex", action="store_true", help="Shlex the lines. (SHell LEXical Analysis)", default=False)
parser.add_argument("--version", action="store_true", help="Show the Meshbook version.")
return parser
async def init_connection(credentials: dict) -> meshctrl.Session:
'''
Use the libmeshctrl library to initiate a Secure Websocket (wss) connection to the MeshCentral instance.
'''
if "totp_secret" in credentials:
totp = pyotp.TOTP(credentials["totp_secret"])
otp = totp.now()
session = meshctrl.Session(
credentials['hostname'],
user=credentials['username'],
password=credentials['password'],
token=otp
)
else:
session = meshctrl.Session(
credentials['hostname'],
user=credentials['username'],
password=credentials['password']
)
await session.initialized.wait()
return session
async def main():
just_fix_windows_console()
'''
Main function where the program starts. Place from which all comands originate (eventually).
'''
# Define the cmd arguments
parser = define_cmdargs()
args = parser.parse_args()
if args.version:
Console.print_text(args.silent,
Console.text_color.reset + "MeshBook Version: " + Console.text_color.yellow + str(meshbook_version))
return
if not args.oscategories:
local_categories_file = "./os_categories.json"
else:
local_categories_file = args.oscategories
if not args.meshbook:
parser.print_help()
return
try:
with open(local_categories_file, "r") as file:
os_categories = json.load(file)
if not Utilities.path_exist(args.meshbook) or Utilities.path_type(args.meshbook) != "File":
Console.print_text(args.silent,
Console.text_color.red + "The given meshbook path is either not present on the filesystem or not a file.")
return
credentials, meshbook = await asyncio.gather(
(Utilities.load_config(args)),
(Utilities.compile_book(args.meshbook))
)
if args.group != "":
meshbook["group"] = args.group
if "device" in meshbook:
del meshbook["device"]
if "devices" in meshbook:
del meshbook["devices"]
elif args.device != "":
meshbook["device"] = args.device
if "group" in meshbook:
del meshbook["group"]
if "groups" in meshbook:
del meshbook["groups"]
'''
The following section mainly displays used variables and first steps of the program to the Console.
'''
# INIT ARGUMENTS PRINTING
Console.print_line(args.silent)
Console.print_text(args.silent,
"meshbook: " + Console.text_color.yellow + args.meshbook + Console.text_color.reset + ".")
Console.print_text(args.silent,
"Operating System Categorisation file: " + Console.text_color.yellow + args.oscategories + Console.text_color.reset + ".")
Console.print_text(args.silent,
"Configuration file: " + Console.text_color.yellow + args.conf + Console.text_color.reset + ".")
# TARGET OS PRINTING
if "target_os" in meshbook:
Console.print_text(args.silent,
"Target Operating System category given: " + Console.text_color.yellow + meshbook["target_os"] + Console.text_color.reset + ".")
else:
Console.print_text(args.silent,
"Target Operating System category given: " + Console.text_color.yellow + "All" + Console.text_color.reset + ".")
# Should Meshbook ignore categorisation?
if "ignore_categorisation" in meshbook:
Console.print_text(args.silent,
"Ignore the OS Categorisation file: " + Console.text_color.yellow + str(meshbook["ignore_categorisation"]) + Console.text_color.reset + ".")
if meshbook["ignore_categorisation"]:
Console.print_text(args.silent,
Console.text_color.red + "!!!!\n" +
Console.text_color.yellow +
"Ignore categorisation is True.\nThis means that the program checks if the target Operating System is somewhere in the reported device Operating System." +
Console.text_color.red + "\n!!!!")
else:
Console.print_text(args.silent,
"Ignore the OS Categorisation file: " + Console.text_color.yellow + "False" + Console.text_color.reset + ".")
# TARGET TAG PRINTING
if "target_tag" in meshbook:
Console.print_text(args.silent,
"Target Device tag given: " + Console.text_color.yellow + meshbook["target_tag"] + Console.text_color.reset + ".")
else:
Console.print_text(args.silent,
"Target Device tag given: " + Console.text_color.yellow + "All" + Console.text_color.reset + ".")
# TARGET PRINTING
if "device" in meshbook:
Console.print_text(args.silent,
"Target device: " + Console.text_color.yellow + str(meshbook["device"]) + Console.text_color.reset + ".")
elif "devices" in meshbook:
Console.print_text(args.silent,
"Target devices: " + Console.text_color.yellow + str(meshbook["devices"]) + Console.text_color.reset + ".")
elif "group" in meshbook:
Console.print_text(args.silent,
"Target group: " + Console.text_color.yellow + str(meshbook["group"]) + Console.text_color.reset + ".")
elif "groups" in meshbook:
Console.print_text(args.silent,
"Target groups: " + Console.text_color.yellow + str(meshbook["groups"]) + Console.text_color.reset + ".")
# RUNNING PARAMETERS PRINTING
Console.print_text(args.silent, "Grace: " + Console.text_color.yellow + str(not args.nograce) + Console.text_color.reset + ".") # Negation of bool for correct explanation
Console.print_text(args.silent, "Silent: " + Console.text_color.yellow + "False" + Console.text_color.reset + ".") # Can be pre-defined because if silent flag was passed then none of this would be printed.
session = await init_connection(credentials)
# PROCESS PRINTING aka what its doing in the moment...
Console.print_line(args.silent)
Console.print_text(args.silent,
Console.text_color.italic + "Trying to load the MeshCentral account credential file...")
Console.print_text(args.silent,
Console.text_color.italic + "Trying to load the meshbook yaml file and compile it into something workable...")
Console.print_text(args.silent,
Console.text_color.italic + "Trying to load the Operating System categorisation JSON file...")
Console.print_text(args.silent,
Console.text_color.italic + "Connecting to MeshCentral and establish a session using variables from previous credential file.")
Console.print_text(args.silent,
Console.text_color.italic + "Generating group list with nodes and reference the targets from that.")
'''
End of the main information displaying section.
'''
group_list = await Transform.compile_group_list(session)
compiled_device_list = await Utilities.gather_targets(args.silent, meshbook, group_list, os_categories)
# Check if we have reachable targets on the MeshCentral host
if "target_list" not in compiled_device_list or len(compiled_device_list["target_list"]) == 0:
Console.print_text(args.silent,
Console.text_color.red + "No targets found or targets unreachable, quitting.")
Console.print_line(args.silent)
return
Console.print_line(args.silent)
match meshbook:
case {"group": candidate_target_name}:
target_name = candidate_target_name
case {"groups": candidate_target_name}:
target_name = str(candidate_target_name)
case {"device": candidate_target_name}:
target_name = candidate_target_name
case {"devices": candidate_target_name}:
target_name = str(candidate_target_name)
case _:
target_name = ""
# Initialize the history / logging functions class (whatever you want to name it)
history = History(args.silent, args.historydir, args.flushhistory)
# Conclude history initlialization
Console.print_line(args.silent)
# From here on the actual exection happens
Console.print_text(args.silent,
Console.text_color.yellow + "Executing meshbook on the target(s): " + Console.text_color.green + target_name + Console.text_color.yellow + ".")
if not args.nograce:
Console.print_text(args.silent,
Console.text_color.yellow + "Initiating grace-period...")
for x in range(grace_period):
Console.print_text(args.silent,
Console.text_color.yellow + "{}...".format(x+1)) # Countdown!
await asyncio.sleep(1)
Console.print_line(args.silent)
complete_log = await Executor.execute_meshbook(args.silent,
args.shlex,
session,
compiled_device_list,
meshbook,
group_list)
Console.print_line(args.silent)
indent = None
if args.indent: indent = 4
formatted_history = json.dumps(complete_log,indent=indent)
Console.print_text(args.silent, formatted_history, 9)
# Pass the output of the whole program to the history class
if args.nohistory:
Console.print_text(args.silent, "Not writing to file.")
else:
Console.print_text(args.silent, "Writing to file...")
history.write_history(formatted_history)
except OSError as message:
Console.print_text(
args.silent,
Console.text_color.red + f'{message}'
)
except asyncio.CancelledError:
Console.print_text(
args.silent,
Console.text_color.red + "Received SIGINT, Aborting - (Tasks may still be running on targets)."
)
raise
finally:
await session.close()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
Console.print_text(False, Console.text_color.red + "Cancelled execution.")