ptmanager 0.0.1__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.
- ptmanager/__init__.py +0 -0
- ptmanager/_version.py +1 -0
- ptmanager/modules/__init__.py +0 -0
- ptmanager/modules/config.py +95 -0
- ptmanager/modules/process.py +25 -0
- ptmanager/modules/process_manager.py +338 -0
- ptmanager/modules/project_manager.py +195 -0
- ptmanager/modules/tools_manager.py +133 -0
- ptmanager/ptmanager.py +182 -0
- ptmanager-0.0.1.dist-info/LICENSE +674 -0
- ptmanager-0.0.1.dist-info/METADATA +105 -0
- ptmanager-0.0.1.dist-info/RECORD +15 -0
- ptmanager-0.0.1.dist-info/WHEEL +5 -0
- ptmanager-0.0.1.dist-info/entry_points.txt +2 -0
- ptmanager-0.0.1.dist-info/top_level.txt +1 -0
ptmanager/__init__.py
ADDED
|
File without changes
|
ptmanager/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|
|
File without changes
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from ptlibs import ptprinthelper
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
|
|
6
|
+
class Config:
|
|
7
|
+
NAME = "config.json"
|
|
8
|
+
PROJECTS_KEY = "projects"
|
|
9
|
+
TEMP = "temp"
|
|
10
|
+
SATID_KEY = "satid"
|
|
11
|
+
PID_KEY = "pid"
|
|
12
|
+
|
|
13
|
+
def __init__(self, config_path: str) -> None:
|
|
14
|
+
self._config_path = config_path
|
|
15
|
+
self._config: dict[list] = None
|
|
16
|
+
try:
|
|
17
|
+
self.load()
|
|
18
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
19
|
+
ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Config file not found, creating new ..\n", "ERROR"))
|
|
20
|
+
self.make()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load(self) -> dict[list]:
|
|
24
|
+
with open(self._config_path + self.NAME) as f:
|
|
25
|
+
self._config = json.load(f)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def make(self) -> dict[list]:
|
|
29
|
+
self.assure_config_path()
|
|
30
|
+
with open(self._config_path + self.NAME, "w+") as f:
|
|
31
|
+
data = {self.SATID_KEY: None, self.PROJECTS_KEY: []}
|
|
32
|
+
f.write(json.dumps(data, indent=4, sort_keys=True))
|
|
33
|
+
self._config = json.loads(json.dumps(data))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def assure_config_path(self) -> None:
|
|
37
|
+
os.makedirs(self._config_path, exist_ok=True)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def delete(self) -> None:
|
|
41
|
+
os.remove(self._config_path + self.NAME)
|
|
42
|
+
|
|
43
|
+
def delete_projects(self) -> None:
|
|
44
|
+
shutil.rmtree(os.path.join(self._config_path, self.PROJECTS_KEY))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def save(self) -> None:
|
|
48
|
+
with open(self._config_path + self.NAME, "w") as f:
|
|
49
|
+
json.dump(self._config, f, indent=4)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_path(self) -> str:
|
|
53
|
+
return self._config_path
|
|
54
|
+
|
|
55
|
+
def get_temp_path(self) -> str:
|
|
56
|
+
temp_path = self._config_path + self.TEMP + "/"
|
|
57
|
+
os.makedirs(temp_path, exist_ok=True)
|
|
58
|
+
return temp_path
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def print(self) -> None:
|
|
62
|
+
print("-"*40, json.dumps(self._config, indent=4), "-"*40, sep="\n")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_projects(self) -> list:
|
|
66
|
+
return self._config[self.PROJECTS_KEY]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_satid(self) -> str:
|
|
70
|
+
return self._config[self.SATID_KEY]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def set_satid(self, UID) -> None:
|
|
74
|
+
self._config[self.SATID_KEY] = UID
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def add_project(self, project: dict[str]) -> None:
|
|
78
|
+
self._config[self.PROJECTS_KEY].append(project)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_pid(self, project_id):
|
|
82
|
+
return self._config[self.PROJECTS_KEY][project_id][self.PID_KEY]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def set_project_pid(self, project_id: int, pid: int) -> None:
|
|
86
|
+
self._config[self.PROJECTS_KEY][project_id][self.PID_KEY] = pid
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def remove_project(self, project_id: int) -> None:
|
|
90
|
+
shutil.rmtree(os.path.join(self._config_path, self.PROJECTS_KEY, self.get_project(project_id).get("AS-ID")))
|
|
91
|
+
self._config[self.PROJECTS_KEY].pop(project_id)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_project(self, project_id: int):
|
|
95
|
+
return self._config[self.PROJECTS_KEY][project_id]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import signal
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Process:
|
|
6
|
+
def __init__(self, PID: int) -> None:
|
|
7
|
+
self.PID = PID
|
|
8
|
+
|
|
9
|
+
def is_running(self) -> bool:
|
|
10
|
+
"Check if process is running"
|
|
11
|
+
if self.PID is None:
|
|
12
|
+
return False
|
|
13
|
+
try:
|
|
14
|
+
os.kill(self.PID, 0) # Does not terminate, just checks if running
|
|
15
|
+
return True
|
|
16
|
+
except ProcessLookupError:
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
def kill(self) -> bool:
|
|
20
|
+
"Tries to kill process with PID"
|
|
21
|
+
try:
|
|
22
|
+
os.kill(self.PID, signal.SIGTERM) # Tries to kill process
|
|
23
|
+
return True
|
|
24
|
+
except ProcessLookupError:
|
|
25
|
+
return False
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json; from json.decoder import JSONDecodeError
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys; sys.path.extend([__file__.rsplit("/", 1)[0], os.path.join(__file__.rsplit("/", 1)[0], "modules")])
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
import pathlib
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
from process import Process
|
|
13
|
+
from config import Config
|
|
14
|
+
|
|
15
|
+
class ProcessManager:
|
|
16
|
+
|
|
17
|
+
def __init__(self, args):
|
|
18
|
+
self.config: Config = Config(f"{str(pathlib.Path.home())}/.ptmanager/")
|
|
19
|
+
self.satid: str = self.config.get_satid()
|
|
20
|
+
self.target: str = args.target
|
|
21
|
+
self.API_PATH: str = "api/v1/sat/"
|
|
22
|
+
|
|
23
|
+
self.AS_ID: str = args.project_id
|
|
24
|
+
self.project_dir: str = os.path.join(self.config.get_path(), "projects", self.AS_ID)
|
|
25
|
+
self.project_tasks_file: str = os.path.join(self.project_dir, "tasks.json")
|
|
26
|
+
|
|
27
|
+
self.no_ssl_verify = args.no_ssl_verify
|
|
28
|
+
self.proxies: dict = {"http": args.proxy, "https": args.proxy}
|
|
29
|
+
self.free_threads = [i for i in range(args.threads)]
|
|
30
|
+
self.threads_list = ["" for _ in range(args.threads)]
|
|
31
|
+
self.lock = threading.Lock()
|
|
32
|
+
|
|
33
|
+
if not args.target or not args.auth or not args.sid:
|
|
34
|
+
self.ptjsonlib.end_error(f"Target, auth and sid are required", self.use_json)
|
|
35
|
+
|
|
36
|
+
if not os.path.isdir(self.project_dir):
|
|
37
|
+
os.makedirs(self.project_dir)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def run(self, args):
|
|
41
|
+
self.process_front(args.target, args.auth)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def process_front(self, target, auth) -> None:
|
|
45
|
+
while True:
|
|
46
|
+
while not self.free_threads:
|
|
47
|
+
time.sleep(8)
|
|
48
|
+
|
|
49
|
+
self.send_results_to_server(target)
|
|
50
|
+
|
|
51
|
+
task = self.get_task_from_as(target, auth)
|
|
52
|
+
if not task:
|
|
53
|
+
time.sleep(10)
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
elif task["action"] == "new_task":
|
|
57
|
+
thread_no = self.free_threads.pop()
|
|
58
|
+
self.threads_list[thread_no] = threading.Thread(target=self.process_newtask, name=task["guid"], args=(task, thread_no), daemon=False)
|
|
59
|
+
self.threads_list[thread_no].start()
|
|
60
|
+
elif task["action"] == "status":
|
|
61
|
+
self.status_task(task)
|
|
62
|
+
elif task["action"] == "status-all":
|
|
63
|
+
self.status_all_tasks()
|
|
64
|
+
elif task["action"] == "kill-task":
|
|
65
|
+
self.kill_task(task)
|
|
66
|
+
elif task["action"] == "kill-all":
|
|
67
|
+
self.kill_all_tasks()
|
|
68
|
+
elif task["action"] == "null":
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def send_results_to_server(self, target) -> None:
|
|
73
|
+
self.lock.acquire()
|
|
74
|
+
with self.touchopen(self.project_tasks_file, "r+") as tasks_file:
|
|
75
|
+
try:
|
|
76
|
+
tasks_list: list = json.load(tasks_file)
|
|
77
|
+
except JSONDecodeError:
|
|
78
|
+
tasks_list = []
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
for task_idx, task in enumerate(tasks_list):
|
|
82
|
+
if task["status"] == "running":
|
|
83
|
+
continue
|
|
84
|
+
task_result_file = os.path.join(self.project_dir, task["guid"])
|
|
85
|
+
if os.path.isfile(task_result_file):
|
|
86
|
+
try:
|
|
87
|
+
with open(task_result_file, "r") as file:
|
|
88
|
+
task_result = json.load(file)
|
|
89
|
+
except (JSONDecodeError, Exception) as e:
|
|
90
|
+
#TODO pokud existuje odkaz na task v tasks.json, ale neexistuje soubor GUID s taskem, nebo je tento soubor vadný, pak se tento záznam nikdy neodstraní z tasks.json
|
|
91
|
+
print("Chyba pri nacitani vysledku automatu ze souboru -", e)
|
|
92
|
+
task_result = {}
|
|
93
|
+
self.lock.release()
|
|
94
|
+
return
|
|
95
|
+
else:
|
|
96
|
+
task_result = None
|
|
97
|
+
|
|
98
|
+
if task_result:
|
|
99
|
+
task_result["guid"] = task["guid"]
|
|
100
|
+
task_result["satid"] = self.satid
|
|
101
|
+
task_result["results"] = json.dumps(task_result["results"])
|
|
102
|
+
response = self.send_to_api(end_point="result", data=(task_result))
|
|
103
|
+
if response.status_code == 200:
|
|
104
|
+
# Remove automat result as it's already been posted to AS.
|
|
105
|
+
try:
|
|
106
|
+
os.remove(task_result_file)
|
|
107
|
+
except OSError as e:
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
# TODO: Popnuto z tasks_listu, ted je potreba upraveny (popnuty) tasks_list zaktualizovat v souboru
|
|
111
|
+
# TODO: Tak, že otevřu znovu <project_tasks_file> a nahradím jeho obsah popnutym tasks_listem.
|
|
112
|
+
tasks_list.pop(task_idx)
|
|
113
|
+
|
|
114
|
+
# Otevře soubor pro zápis - nahradí seznam aktualizovaným tasks_listem.
|
|
115
|
+
with self.touchopen(self.project_tasks_file, "w") as tasks_file:
|
|
116
|
+
json.dumps(tasks_list, indent=4)
|
|
117
|
+
|
|
118
|
+
self.lock.release()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def send_to_api(self, end_point, data) -> requests.Response:
|
|
122
|
+
target = self.target + self.API_PATH + end_point
|
|
123
|
+
response = requests.post(target, data=json.dumps(data), proxies=self.proxies, verify=self.no_ssl_verify, headers={"Content-Type": "application/json"})
|
|
124
|
+
#if response.status_code != 200:
|
|
125
|
+
#print(f"Response status code is not 200, but {response.status_code}")
|
|
126
|
+
return response
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def status_task(self, task) -> None:
|
|
130
|
+
"""Retrieve status of <task>, repairs tasks.json if task is not running"""
|
|
131
|
+
self.lock.acquire()
|
|
132
|
+
with self.touchopen(self.project_tasks_file, "r+") as tasks_list_file:
|
|
133
|
+
tasks_list = json.loads(tasks_list_file.read())
|
|
134
|
+
for index, dictionary in enumerate(tasks_list):
|
|
135
|
+
if dictionary["guid"] == task["guid"]:
|
|
136
|
+
if not Process(tasks_list[index]["pid"]).is_running():
|
|
137
|
+
tasks_list[index]["status"] = "error"
|
|
138
|
+
tasks_list[index]["pid"] = None
|
|
139
|
+
tasks_list_file.seek(0)
|
|
140
|
+
tasks_list_file.truncate(0)
|
|
141
|
+
tasks_list_file.write(json.dumps(tasks_list, indent=4))
|
|
142
|
+
self.lock.release()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def status_all_tasks(self) -> None:
|
|
146
|
+
"""Repairs all tasks."""
|
|
147
|
+
self.lock.acquire()
|
|
148
|
+
with self.touchopen(self.project_tasks_file, "r+") as tasks_list_file:
|
|
149
|
+
try:
|
|
150
|
+
tasks_list = json.loads(tasks_list_file.read())
|
|
151
|
+
for task in tasks_list:
|
|
152
|
+
if not Process(task["pid"]).is_running():
|
|
153
|
+
task["status"] = "error"
|
|
154
|
+
task["pid"] = None
|
|
155
|
+
tasks_list_file.seek(0)
|
|
156
|
+
tasks_list_file.truncate(0)
|
|
157
|
+
tasks_list_file.write(json.dumps(tasks_list, indent=4))
|
|
158
|
+
except JSONDecodeError:
|
|
159
|
+
pass
|
|
160
|
+
finally:
|
|
161
|
+
self.lock.release()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def kill_all_tasks(self) -> None:
|
|
165
|
+
"""Kills all tasks."""
|
|
166
|
+
|
|
167
|
+
for t in self.threads_list:
|
|
168
|
+
if isinstance(t, threading.Thread):
|
|
169
|
+
t.join()
|
|
170
|
+
|
|
171
|
+
for file in os.listdir(self.project_dir):
|
|
172
|
+
if file != "tasks.json":
|
|
173
|
+
os.remove(os.path.join(self.project_dir, file))
|
|
174
|
+
|
|
175
|
+
# TODO - KillAllThreads - Pockat nez se thready dokonci, nebo chladnokrevne zavrazdit vsechno co je thread?
|
|
176
|
+
self.lock.acquire()
|
|
177
|
+
with self.touchopen(self.project_tasks_file, "r+") as tasks_list_file:
|
|
178
|
+
try:
|
|
179
|
+
tasks_list = json.loads(tasks_list_file.read())
|
|
180
|
+
for task in tasks_list:
|
|
181
|
+
if task["pid"]:
|
|
182
|
+
Process(task["pid"]).kill()
|
|
183
|
+
task["status"] = "killed"
|
|
184
|
+
task["pid"] = None
|
|
185
|
+
tasks_list_file.seek(0)
|
|
186
|
+
tasks_list_file.truncate(0)
|
|
187
|
+
tasks_list_file.write(json.dumps(tasks_list, indent=4))
|
|
188
|
+
except JSONDecodeError:
|
|
189
|
+
pass
|
|
190
|
+
finally:
|
|
191
|
+
self.lock.release()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def kill_task(self, task) -> None:
|
|
195
|
+
"""Kills task with supplied guid."""
|
|
196
|
+
for t in self.threads_list:
|
|
197
|
+
if isinstance(t, threading.Thread) and t.name == task["guid"]:
|
|
198
|
+
t.join()
|
|
199
|
+
try:
|
|
200
|
+
os.remove(os.path.join(self.project_dir, task["guid"]))
|
|
201
|
+
except OSError:
|
|
202
|
+
# File Not Found
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
self.lock.acquire()
|
|
206
|
+
with self.touchopen(self.project_tasks_file, "r+") as tasks_list_file:
|
|
207
|
+
try:
|
|
208
|
+
tasks_list = json.loads(tasks_list_file.read())
|
|
209
|
+
for task_in_list in tasks_list:
|
|
210
|
+
if task_in_list["guid"] == task["guid"]:
|
|
211
|
+
if task_in_list["pid"]:
|
|
212
|
+
Process(task_in_list["pid"]).kill()
|
|
213
|
+
task_in_list["status"] = "killed"
|
|
214
|
+
task_in_list["pid"] = None
|
|
215
|
+
tasks_list_file.seek(0)
|
|
216
|
+
tasks_list_file.truncate(0)
|
|
217
|
+
tasks_list_file.write(json.dumps(tasks_list, indent=4))
|
|
218
|
+
except JSONDecodeError:
|
|
219
|
+
pass
|
|
220
|
+
finally:
|
|
221
|
+
self.lock.release()
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def touchopen(self, filename, mode):
|
|
225
|
+
open(filename, "a").close() # "touch" file
|
|
226
|
+
return open(filename, mode)
|
|
227
|
+
|
|
228
|
+
def process_newtask(self, task, thread_no) -> None:
|
|
229
|
+
"""Process newtask with available thread"""
|
|
230
|
+
result_filename = self.config.get_temp_path() + task["guid"]
|
|
231
|
+
with open(result_filename, "w+") as result_file:
|
|
232
|
+
result_file.truncate(0) # Delete file content, if it exist
|
|
233
|
+
tool_subprocess = subprocess.Popen(task["task"].split(), stdout=result_file, text=True)
|
|
234
|
+
thread_variable = {"guid": task["guid"], "pid": tool_subprocess.pid, "timeStamp": time.time(), "status": "running"}
|
|
235
|
+
|
|
236
|
+
self.lock.acquire()
|
|
237
|
+
# Read tasks_list_file
|
|
238
|
+
with self.touchopen(self.project_tasks_file, "r") as tasks_list_file:
|
|
239
|
+
try:
|
|
240
|
+
tasks_list = json.load(tasks_list_file)
|
|
241
|
+
except JSONDecodeError:
|
|
242
|
+
tasks_list = []
|
|
243
|
+
|
|
244
|
+
# Update tasks_list in memory
|
|
245
|
+
tasks_list.append(thread_variable)
|
|
246
|
+
|
|
247
|
+
# Replace content with updated one
|
|
248
|
+
with self.touchopen(self.project_tasks_file, "w") as tasks_list_file:
|
|
249
|
+
tasks_list_file.write(json.dumps(tasks_list, indent=4))
|
|
250
|
+
|
|
251
|
+
self.lock.release()
|
|
252
|
+
|
|
253
|
+
tool_subprocess.wait()
|
|
254
|
+
|
|
255
|
+
thread_variable.update({"status": "finished", "pid": None})
|
|
256
|
+
|
|
257
|
+
with open(result_filename, "r") as result_file:
|
|
258
|
+
tool_result = result_file.read()
|
|
259
|
+
|
|
260
|
+
os.remove(result_filename)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
self.lock.acquire()
|
|
265
|
+
# Read tasks_list_file
|
|
266
|
+
with self.touchopen(self.project_tasks_file, "r") as tasks_list_file:
|
|
267
|
+
tasks_list = json.load(tasks_list_file)
|
|
268
|
+
self.lock.release()
|
|
269
|
+
|
|
270
|
+
for task_idx, task_dict in enumerate(tasks_list):
|
|
271
|
+
if task_dict["guid"] == thread_variable["guid"]:
|
|
272
|
+
tasks_list[task_idx] = thread_variable
|
|
273
|
+
|
|
274
|
+
self.lock.acquire()
|
|
275
|
+
# Replace content with updated one
|
|
276
|
+
with self.touchopen(self.project_tasks_file, "w") as tasks_list_file:
|
|
277
|
+
tasks_list_file.write(json.dumps(tasks_list, indent=4))
|
|
278
|
+
self.lock.release()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
self.lock.acquire()
|
|
282
|
+
with open(os.path.join(self.project_dir, task["guid"]), "w") as task_result_file:
|
|
283
|
+
task_result_file.write(tool_result)
|
|
284
|
+
|
|
285
|
+
self.lock.release()
|
|
286
|
+
self.free_threads.append(thread_no)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def get_task_from_as(self, target=None, auth=None) -> dict:
|
|
290
|
+
"""Retrieve task from AS"""
|
|
291
|
+
try:
|
|
292
|
+
response = self.send_to_api(end_point="tasks", data={"satid": self.satid}).json()
|
|
293
|
+
except requests.RequestException as e:
|
|
294
|
+
print(f"Chyba pri ziskavani tasku z AS - {e}")
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
guid = response["data"]["guid"]
|
|
299
|
+
action = response["data"]["action"]
|
|
300
|
+
command = response["data"]["command"]
|
|
301
|
+
return {"guid": guid, "action": action, "task": command}
|
|
302
|
+
except Exception as e:
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _delete_task_from_tasks(self, task) -> None:
|
|
308
|
+
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), self.AS_ID, "tasks.json"), "r+") as f:
|
|
309
|
+
original_json = json.loads(f.read())
|
|
310
|
+
modified_json = [i for i in original_json if i["guid"] != task["guid"]]
|
|
311
|
+
self.write_to_file_from_start(f, str(modified_json))
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def write_to_file_from_start(self, open_file, data: any) -> None:
|
|
315
|
+
open_file.seek(0)
|
|
316
|
+
open_file.truncate(0)
|
|
317
|
+
open_file.write(data)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def parse_args():
|
|
321
|
+
parser = argparse.ArgumentParser(add_help=False)
|
|
322
|
+
parser.add_argument("-T", "--target", type=str)
|
|
323
|
+
parser.add_argument("-a", "--auth", type=str)
|
|
324
|
+
parser.add_argument("-S", "--sid", type=str)
|
|
325
|
+
parser.add_argument("-prj", "--project_id", type=str)
|
|
326
|
+
parser.add_argument("-p", "--proxy", type=str)
|
|
327
|
+
parser.add_argument("--no_ssl_verify", action="store_false")
|
|
328
|
+
|
|
329
|
+
parser.add_argument("-t", "--threads", type=int, default=20)
|
|
330
|
+
|
|
331
|
+
args = parser.parse_args()
|
|
332
|
+
return args
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
if __name__ == "__main__":
|
|
336
|
+
args = parse_args()
|
|
337
|
+
tool_subprocess = ProcessManager(args)
|
|
338
|
+
tool_subprocess.run(args)
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import random
|
|
3
|
+
import signal
|
|
4
|
+
import string
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys; sys.path.extend([__file__.rsplit("/", 1)[0], os.path.join(__file__.rsplit("/", 1)[0], "modules")])
|
|
7
|
+
import urllib
|
|
8
|
+
import uuid
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
from ptlibs import ptjsonlib, ptprinthelper
|
|
14
|
+
|
|
15
|
+
from config import Config
|
|
16
|
+
from process import Process
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ProjectManager:
|
|
20
|
+
def __init__(self, ptjsonlib: ptjsonlib.PtJsonLib, use_json: bool, proxies: dict, no_ssl_verify: bool, config: Config) -> None:
|
|
21
|
+
self.ptjsonlib = ptjsonlib
|
|
22
|
+
self.use_json = use_json
|
|
23
|
+
self.no_ssl_verify = no_ssl_verify
|
|
24
|
+
self.proxies = {"http": proxies, "https": proxies}
|
|
25
|
+
self.config = config
|
|
26
|
+
|
|
27
|
+
def register_project(self, target: str, auth_token: str) -> None:
|
|
28
|
+
"""Registers new project"""
|
|
29
|
+
if not target:
|
|
30
|
+
self.ptjsonlib.end_error("Missing --target parameter", self.use_json)
|
|
31
|
+
if not auth_token:
|
|
32
|
+
self.ptjsonlib.end_error("Missing --auth parameter", self.use_json)
|
|
33
|
+
|
|
34
|
+
target = self.is_url(target)
|
|
35
|
+
if not target:
|
|
36
|
+
self.ptjsonlib.end_error("Target is not a valid URL", self.use_json)
|
|
37
|
+
|
|
38
|
+
registration_url = target + "api/v1/sat/register"
|
|
39
|
+
try:
|
|
40
|
+
response = requests.post(registration_url, proxies=self.proxies, verify=self.no_ssl_verify, data={"token": auth_token, "satid": self.config.get_satid()})
|
|
41
|
+
except requests.RequestException as e:
|
|
42
|
+
self.ptjsonlib.end_error("Server is not responding", self.use_json)
|
|
43
|
+
|
|
44
|
+
if response.status_code == 200:
|
|
45
|
+
try:
|
|
46
|
+
response_json = response.json()
|
|
47
|
+
except json.JSONDecodeError:
|
|
48
|
+
self.ptjsonlib.end_error(f"Could not parse target response as json - {response.text}", self.use_json)
|
|
49
|
+
|
|
50
|
+
if response_json.get("success"):
|
|
51
|
+
print(response_json["data"]['name'])
|
|
52
|
+
print(response_json['message'])
|
|
53
|
+
project_name = response_json['data']['name']
|
|
54
|
+
else:
|
|
55
|
+
self.ptjsonlib.end_error(response_json, self.use_json)
|
|
56
|
+
else:
|
|
57
|
+
self.ptjsonlib.end_error(f"[{response.status_code}] {response.text}", self.use_json)
|
|
58
|
+
|
|
59
|
+
AS_ID = ''.join(random.choice(string.ascii_lowercase) for _ in range(8))
|
|
60
|
+
self.config.add_project({"project_name": project_name, "target": target, "auth": auth_token, "pid": None, "AS-ID": AS_ID})
|
|
61
|
+
|
|
62
|
+
def is_url(self, url: int):
|
|
63
|
+
try:
|
|
64
|
+
result = urllib.parse.urlparse(url)
|
|
65
|
+
if result.path:
|
|
66
|
+
while result.path.endswith("/"):
|
|
67
|
+
result = result._replace(path=result.path[:-1])
|
|
68
|
+
result = result._replace(path=result.path + "/")
|
|
69
|
+
return result.geturl()
|
|
70
|
+
except ValueError:
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
def start_project(self, project_id: int) -> None:
|
|
74
|
+
"""Starts specified project."""
|
|
75
|
+
print("Starting ....")
|
|
76
|
+
project = self.config.get_project(project_id)
|
|
77
|
+
if project["pid"]:
|
|
78
|
+
if not Process(project["pid"]).is_running():
|
|
79
|
+
self.config.set_project_pid(project_id, None)
|
|
80
|
+
else:
|
|
81
|
+
self.ptjsonlib.end_error(f"Project is already running with PID {project['pid']}", self.use_json)
|
|
82
|
+
|
|
83
|
+
response = {"sessionid": "sessionid"}
|
|
84
|
+
try:
|
|
85
|
+
subprocess_args = [sys.executable, os.path.realpath(os.path.join(__file__.rsplit("/", 1)[0], "process_manager.py")), "--target", project["target"], "--auth", project["auth"], "--sid", response["sessionid"], "--project_id", project["AS-ID"]]
|
|
86
|
+
|
|
87
|
+
if self.proxies.get("http"):
|
|
88
|
+
subprocess_args += ["--proxy", self.proxies.get("http")]
|
|
89
|
+
if not self.no_ssl_verify:
|
|
90
|
+
subprocess_args += ["--no_ssl_verify"]
|
|
91
|
+
|
|
92
|
+
process = subprocess.Popen(subprocess_args)
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
self.ptjsonlib.end_error(e, self.use_json)
|
|
96
|
+
self.config.set_project_pid(project_id, process.pid)
|
|
97
|
+
ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Started Project {project_id+1} with PID {process.pid}", "INFO"))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def end_project(self, project_id: int) -> None:
|
|
101
|
+
process_pid = self.config.get_pid(project_id)
|
|
102
|
+
if process_pid:
|
|
103
|
+
try:
|
|
104
|
+
os.kill(process_pid, signal.SIGTERM)
|
|
105
|
+
self.config.set_project_pid(project_id, None)
|
|
106
|
+
ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Killed process with PID {process_pid}", "OK"))
|
|
107
|
+
except ProcessLookupError:
|
|
108
|
+
ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Proccess [{process_pid}] is not running ", "ERROR"))
|
|
109
|
+
self.config.set_project_pid(project_id, None)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
self.ptjsonlib.end_error(e, self.use_json)
|
|
112
|
+
else:
|
|
113
|
+
self.ptjsonlib.end_error(f"Project is not running", self.use_json)
|
|
114
|
+
|
|
115
|
+
def reset_project(self, project_id: int) -> None:
|
|
116
|
+
if self.config.get_pid(project_id):
|
|
117
|
+
self.end_project(project_id)
|
|
118
|
+
self.start_project(project_id)
|
|
119
|
+
else:
|
|
120
|
+
self.start_project(project_id)
|
|
121
|
+
|
|
122
|
+
ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Succesfully registered project", "OK"))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def delete_project(self, project_id: int) -> None:
|
|
126
|
+
# TODO: Send request to delete project from AS
|
|
127
|
+
if self.config.get_pid(project_id):
|
|
128
|
+
self.ptjsonlib.end_error(f"Project is running, end project first", self.use_json)
|
|
129
|
+
|
|
130
|
+
project = self.config.get_project(project_id)
|
|
131
|
+
request_path = project["target"] + "api/v1/sat/delete"
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
response = requests.post(request_path, proxies=self.proxies, verify=self.no_ssl_verify, data={"satid": self.config.get_satid()}) #project["auth"]
|
|
135
|
+
self.config.remove_project(project_id)
|
|
136
|
+
ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Project deleted succesfully", "OK"))
|
|
137
|
+
except requests.RequestException as e:
|
|
138
|
+
ptprinthelper.ptprint(f"Server is not responding", "ERROR")
|
|
139
|
+
if self._yes_no_prompt("Cannot delete project from AS. Delete TS from server manualy.\nProject will be deleted locally only. Are you sure?", bullet_type=None):
|
|
140
|
+
self.config.remove_project(project_id)
|
|
141
|
+
ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"Local project deleted succesfully", "OK"))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def list_projects(self) -> None:
|
|
145
|
+
print(f"{ptprinthelper.get_colored_text('ID', 'TITLE')}{' '*4}{ptprinthelper.get_colored_text('Project Name', 'TITLE')}{' '*20}{ptprinthelper.get_colored_text('PID', 'TITLE')}{' '*7}{ptprinthelper.get_colored_text('Status', 'TITLE')}{' '*9}")
|
|
146
|
+
print(f"{'-'*6}{'-'*32}{'-'*10}{'-'*15}")
|
|
147
|
+
if not self.config.get_projects():
|
|
148
|
+
print(" ")
|
|
149
|
+
self.ptjsonlib.end_error("No projects found, register a project first", self.use_json)
|
|
150
|
+
|
|
151
|
+
for index, project in enumerate(self.config.get_projects(), 1):
|
|
152
|
+
if project["pid"]:
|
|
153
|
+
if not Process(project["pid"]).is_running():
|
|
154
|
+
self.config.set_project_pid(index - 1, None)
|
|
155
|
+
project["pid"] = None
|
|
156
|
+
pid = project["pid"]
|
|
157
|
+
if pid:
|
|
158
|
+
status = "running"
|
|
159
|
+
if not pid:
|
|
160
|
+
status = "-"
|
|
161
|
+
pid = "-"
|
|
162
|
+
|
|
163
|
+
print(f"{index}{' '*(6-len(str(index)))}", end="")
|
|
164
|
+
print(f"{project['project_name']}{' '*(32-len(project['project_name']))}", end="")
|
|
165
|
+
print(f"{str(pid)}{' '*(10-len(str(pid)))}", end="")
|
|
166
|
+
print(f"{status}{' '*(15-len(status))}", end="")
|
|
167
|
+
print("")
|
|
168
|
+
|
|
169
|
+
def register_uid(self) -> None:
|
|
170
|
+
UID = str(uuid.uuid1())
|
|
171
|
+
if self.config.get_satid():
|
|
172
|
+
if self._yes_no_prompt("UID already exists, are you sure you want to create new? All your projects will be deleted!"):
|
|
173
|
+
self.config.delete_projects()
|
|
174
|
+
self.config.delete()
|
|
175
|
+
self.config.make()
|
|
176
|
+
self.config.set_satid(UID)
|
|
177
|
+
else:
|
|
178
|
+
exit()
|
|
179
|
+
else:
|
|
180
|
+
self.config.set_satid(UID)
|
|
181
|
+
print("[*] New UID generated")
|
|
182
|
+
self.config.print()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _yes_no_prompt(self, msg, bullet_type="OK") -> bool:
|
|
186
|
+
ptprinthelper.ptprint_(ptprinthelper.out_ifnot(f"{msg}", bullet_type), end=" ")
|
|
187
|
+
reply = input(f"y/N: ").upper().strip()
|
|
188
|
+
if reply == "Y":
|
|
189
|
+
return True
|
|
190
|
+
elif reply == "N" or reply == "":
|
|
191
|
+
return False
|
|
192
|
+
else:
|
|
193
|
+
return self._yes_no_prompt(msg)
|
|
194
|
+
|
|
195
|
+
#TODO: Implementovat přepínač pro insecure SSL a upravit všechny requesty, aby s tímto přepínačem spolupracovaly
|