Merge tag 'upstream/1.0.21' into kali/master
Upstream version 1.0.21
Sophie Brun
7 years ago
11 | 11 | |
12 | 12 | Plugins |
13 | 13 | --- |
14 | Don't change the way you work today! Faraday plays well with others, right now it has more than [40 supported tools](https://github.com/infobyte/faraday/wiki/Plugin-List), among them you will find: | |
14 | Don't change the way you work today! Faraday plays well with others, right now it has more than [50 supported tools](https://github.com/infobyte/faraday/wiki/Plugin-List), among them you will find: | |
15 | 15 | |
16 | 16 | ![](https://raw.github.com/wiki/infobyte/faraday/images/plugins/Plugins.png) |
17 | 17 |
8 | 8 | |
9 | 9 | New features in the latest update |
10 | 10 | ===================================== |
11 | ||
12 | Jun 13, 2016: | |
13 | --- | |
14 | * Added Import Report dialog to Faraday GTK | |
15 | * Added a 'Loading workspace...' dialog to Faraday GTK | |
16 | * Added host sidebar to Faraday GTK | |
17 | * Added host information dialog to Faraday GTK with the full data about a host, its interfaces, services and vulnerabilities | |
18 | * Added support for run faraday from other directories. | |
19 | * Fixed log reapparing after being disabled if user created a new tab | |
20 | * Fixed bug regarding exception handling in Faraday GTK | |
21 | * Now Faraday GTK supports Ctrl+Shift+C / Ctrl+Shift+V to Copy/Paste | |
22 | * Faraday will now not crash if you suddenly lose connection to your CouchDB | |
11 | 23 | |
12 | 24 | May 23, 2016: |
13 | 25 | --- |
1 | 1 | <faraday> |
2 | 2 | |
3 | 3 | <appname>Faraday - Penetration Test IDE</appname> |
4 | <version>1.0.20</version> | |
4 | <version>1.0.21</version> | |
5 | 5 | <debug_status>0</debug_status> |
6 | 6 | <font>-Misc-Fixed-medium-r-normal-*-12-100-100-100-c-70-iso8859-1</font> |
7 | 7 | <home_path>~/</home_path> |
4 | 4 | |
5 | 5 | ''' |
6 | 6 | |
7 | CONST_VERSION_FILE = 'VERSION' | |
7 | 8 | CONST_REQUIREMENTS_FILE = 'requirements.txt' |
8 | 9 | CONST_CONFIG = 'views/reports/_attachments/scripts/config/config.json' |
9 | 10 | CONST_FARADAY_HOME_PATH = '~/.faraday' |
59 | 59 | USER_QTRCBAK = os.path.expanduser(CONST_USER_QTRC_BACKUP) |
60 | 60 | FARADAY_QTRC = os.path.join(FARADAY_BASE, CONST_FARADAY_QTRC_PATH) |
61 | 61 | FARADAY_QTRCBAK = os.path.expanduser(CONST_FARADAY_QTRC_BACKUP) |
62 | CONST_VERSION_FILE = os.path.join(FARADAY_BASE,"VERSION") | |
62 | FARADAY_VERSION_FILE = os.path.join(FARADAY_BASE, CONST_VERSION_FILE) | |
63 | FARADAY_CONFIG = os.path.join(FARADAY_BASE, CONST_CONFIG) | |
64 | FARADAY_REQUIREMENTS_FILE = os.path.join(FARADAY_BASE, CONST_REQUIREMENTS_FILE) | |
63 | 65 | |
64 | 66 | REQUESTS_CA_BUNDLE_VAR = "REQUESTS_CA_BUNDLE" |
65 | 67 | FARADAY_DEFAULT_PORT_XMLRPC = 9876 |
213 | 215 | try: |
214 | 216 | import pip |
215 | 217 | modules = [] |
216 | f = open(CONST_REQUIREMENTS_FILE) | |
218 | f = open(FARADAY_REQUIREMENTS_FILE) | |
217 | 219 | for line in f: |
218 | 220 | if not line.find('#'): |
219 | 221 | break |
536 | 538 | uri = getInstanceConfiguration().getUpdatesUri() |
537 | 539 | resp = u"OK" |
538 | 540 | try: |
539 | f = open(CONST_VERSION_FILE) | |
541 | f = open(FARADAY_VERSION_FILE) | |
540 | 542 | |
541 | 543 | getInstanceConfiguration().setVersion(f.read().strip()) |
542 | 544 | getInstanceConfiguration().setAppname("Faraday - Penetration Test IDE Community") |
570 | 572 | |
571 | 573 | def checkVersion(): |
572 | 574 | try: |
573 | f = open(CONST_VERSION_FILE) | |
575 | f = open(FARADAY_VERSION_FILE) | |
574 | 576 | f_version = f.read().strip() |
575 | 577 | if not args.update: |
576 | 578 | if getInstanceConfiguration().getVersion() != None and getInstanceConfiguration().getVersion() != f_version: |
583 | 585 | |
584 | 586 | doc = {"ver": getInstanceConfiguration().getVersion()} |
585 | 587 | |
586 | if os.path.isfile(CONST_CONFIG): | |
587 | os.remove(CONST_CONFIG) | |
588 | with open(CONST_CONFIG, "w") as doc_file: | |
588 | if os.path.isfile(FARADAY_CONFIG): | |
589 | os.remove(FARADAY_CONFIG) | |
590 | with open(FARADAY_CONFIG, "w") as doc_file: | |
589 | 591 | json.dump(doc, doc_file) |
590 | 592 | except Exception as e: |
591 | 593 | getLogger("launcher").error("It seems that something's wrong with your version\nPlease contact customer support") |
615 | 617 | Main function for launcher. |
616 | 618 | |
617 | 619 | """ |
620 | ||
621 | os.chdir(FARADAY_BASE) | |
618 | 622 | |
619 | 623 | init() |
620 | 624 | if checkDependencies(): |
8 | 8 | |
9 | 9 | import os |
10 | 10 | import sys |
11 | import threading | |
11 | 12 | |
12 | 13 | try: |
13 | 14 | import gi |
24 | 25 | " of GTK and VTE. Check install of VTE 2.91 and GTK+3") |
25 | 26 | |
26 | 27 | try: |
28 | # there are several imports not needed here, but they're needed in other | |
29 | # modules. this just checks for every dependence when starting the app | |
27 | 30 | from gi.repository import Gio, Gtk, GdkPixbuf, Vte, GLib, GObject, Gdk |
28 | 31 | except ImportError as e: |
29 | 32 | print ("You are missing some of the required dependencies. " |
48 | 51 | from dialogs import helpDialog |
49 | 52 | from dialogs import ImportantErrorDialog |
50 | 53 | from dialogs import ConflictsDialog |
54 | from dialogs import HostInfoDialog | |
55 | from dialogs import errorDialog | |
51 | 56 | |
52 | 57 | from mainwidgets import Sidebar |
58 | from mainwidgets import WorkspaceSidebar | |
59 | from mainwidgets import HostsSidebar | |
53 | 60 | from mainwidgets import ConsoleLog |
54 | 61 | from mainwidgets import Terminal |
55 | 62 | from mainwidgets import Statusbar |
59 | 66 | |
60 | 67 | CONF = getInstanceConfiguration() |
61 | 68 | |
69 | ||
62 | 70 | class GuiApp(Gtk.Application, FaradayUi): |
63 | 71 | """ |
64 | 72 | Creates the application and has the necesary callbacks to FaradayUi |
65 | Right now handles by itself only the menu, everything is else is | |
66 | appWindow's resposibility as far as the initial UI goes. | |
67 | The dialogs are found inside the dialogs module | |
73 | As far as the GUI goes, this handles only the menu, everything is else is | |
74 | appWindow's resposibility. All logic by the main window should be done | |
75 | here. Some of the logic on the dialogs is implemented in the dialogs own | |
76 | class. Some dialogs are shown by the appwindow to handle errors coming | |
77 | from other threads outside GTK's. | |
68 | 78 | """ |
69 | 79 | |
70 | 80 | def __init__(self, model_controller, plugin_manager, workspace_manager, |
71 | 81 | plugin_controller): |
82 | """Does not do much. Most of the initialization work is actually | |
83 | done by the run() method, as specified in FaradayUi.""" | |
72 | 84 | |
73 | 85 | FaradayUi.__init__(self, |
74 | 86 | model_controller, |
79 | 91 | Gtk.Application.__init__(self, application_id="org.infobyte.faraday", |
80 | 92 | flags=Gio.ApplicationFlags.FLAGS_NONE) |
81 | 93 | |
82 | icons = CONF.getImagePath() + "icons/" | |
83 | faraday_icon = icons + "faraday_icon.png" | |
94 | self.icons = CONF.getImagePath() + "icons/" | |
95 | faraday_icon = self.icons + "faraday_icon.png" | |
84 | 96 | self.icon = GdkPixbuf.Pixbuf.new_from_file_at_scale(faraday_icon, 16, |
85 | 97 | 16, False) |
86 | 98 | self.window = None |
94 | 106 | return self.window |
95 | 107 | |
96 | 108 | def updateConflicts(self): |
109 | """Reassings self.conflicts with an updated list of conflicts""" | |
97 | 110 | self.conflicts = self.model_controller.getConflicts() |
111 | ||
112 | def updateHosts(self): | |
113 | """Reassings the value of self.all_hosts to a current one to | |
114 | catch workspace changes, new hosts added via plugins or any other | |
115 | external interference with out host list""" | |
116 | self.all_hosts = self.model_controller.getAllHosts() | |
98 | 117 | |
99 | 118 | def createWorkspace(self, name, description="", w_type=""): |
100 | 119 | """Pretty much copy/pasted from the QT3 GUI. |
133 | 152 | if CONF.getLastWorkspace() == ws_name: |
134 | 153 | self.openDefaultWorkspace() |
135 | 154 | self.getWorkspaceManager().removeWorkspace(ws_name) |
136 | self.sidebar.clearSidebar() | |
137 | self.sidebar.refreshSidebar() | |
155 | self.ws_sidebar.clearSidebar() | |
156 | self.ws_sidebar.refreshSidebar() | |
138 | 157 | |
139 | 158 | def do_startup(self): |
140 | 159 | """ |
141 | 160 | GTK calls this method after Gtk.Application.run() |
142 | 161 | Creates instances of the sidebar, terminal, console log and |
143 | 162 | statusbar to be added to the app window. |
144 | Sets up necesary acttions on menu and toolbar buttons | |
163 | Sets up necesary actions on menu and toolbar buttons | |
145 | 164 | Also reads the .xml file from menubar.xml |
146 | 165 | """ |
147 | 166 | Gtk.Application.do_startup(self) # deep GTK magic |
148 | 167 | |
149 | self.sidebar = Sidebar(self.workspace_manager, | |
150 | self.changeWorkspace, | |
151 | self.removeWorkspace, | |
152 | self.on_new_button, | |
153 | CONF.getLastWorkspace()) | |
168 | self.ws_sidebar = WorkspaceSidebar(self.workspace_manager, | |
169 | self.changeWorkspace, | |
170 | self.removeWorkspace, | |
171 | self.on_new_button, | |
172 | CONF.getLastWorkspace()) | |
173 | ||
174 | self.updateHosts() | |
175 | self.hosts_sidebar = HostsSidebar(self.show_host_info, self.icons) | |
176 | default_model = self.hosts_sidebar.create_model(self.all_hosts) | |
177 | self.hosts_sidebar.create_view(default_model) | |
178 | ||
179 | self.sidebar = Sidebar(self.ws_sidebar.get_box(), | |
180 | self.hosts_sidebar.get_box()) | |
154 | 181 | |
155 | 182 | host_count, service_count, vuln_count = self.update_counts() |
156 | 183 | |
188 | 215 | |
189 | 216 | action = Gio.SimpleAction.new("new_terminal") # new terminal = new tab |
190 | 217 | action.connect("activate", self.on_new_terminal_button) |
218 | self.add_action(action) | |
219 | ||
220 | action = Gio.SimpleAction.new("open_report") | |
221 | action.connect("activate", self.on_open_report_button) | |
191 | 222 | self.add_action(action) |
192 | 223 | |
193 | 224 | dirname = os.path.dirname(os.path.abspath(__file__)) |
200 | 231 | |
201 | 232 | def do_activate(self): |
202 | 233 | """If there's no window, create one and present it (show it to user). |
203 | If there's a window, just present it""" | |
234 | If there's a window, just present it. Also add the log handler | |
235 | and the notifier to the application""" | |
204 | 236 | |
205 | 237 | # We only allow a single window and raise any existing ones |
206 | 238 | if not self.window: |
226 | 258 | model.guiapi.notification_center.registerWidget(self.window) |
227 | 259 | |
228 | 260 | def postEvent(self, receiver, event): |
261 | """Handles the events from gui/customevents.""" | |
229 | 262 | if receiver is None: |
230 | 263 | receiver = self.getMainWindow() |
231 | 264 | |
232 | if event.type() == 3131: # new log event | |
265 | elif event.type() == 3131: # new log event | |
233 | 266 | receiver.emit("new_log", event.text) |
234 | 267 | |
235 | if event.type() == 3141: # new conflict event | |
268 | elif event.type() == 3141: # new conflict event | |
236 | 269 | receiver.emit("set_conflict_label", event.nconflicts) |
237 | 270 | |
238 | if event.type() == 5100: # new notification event | |
271 | elif event.type() == 5100: # new notification event | |
239 | 272 | self.notificationsModel.prepend([event.change.getMessage()]) |
240 | 273 | receiver.emit("new_notif") |
241 | 274 | host_count, service_count, vuln_count = self.update_counts() |
242 | 275 | receiver.emit("update_ws_info", host_count, |
243 | 276 | service_count, vuln_count) |
244 | 277 | |
245 | if event.type() == 4100 or event.type() == 3140: # newinfo or changews | |
278 | elif event.type() == 4100 or event.type() == 3140: # newinfo or changews | |
246 | 279 | host_count, service_count, vuln_count = self.update_counts() |
280 | self.updateHosts() | |
281 | self.hosts_sidebar.update(self.all_hosts) | |
247 | 282 | receiver.emit("update_ws_info", host_count, |
248 | 283 | service_count, vuln_count) |
249 | 284 | |
250 | if event.type() == 3132: # error | |
251 | dialog_text = event.text | |
252 | dialog = Gtk.MessageDialog(self.window, 0, | |
253 | Gtk.MessageType.INFO, | |
254 | Gtk.ButtonsType.OK, | |
255 | dialog_text) | |
256 | dialog.run() | |
257 | dialog.destroy() | |
258 | ||
259 | if event.type() == 3134: # important error, uncaught exception | |
260 | dialog_text = event.text | |
261 | dialog = ImportantErrorDialog(self.window, dialog_text) | |
262 | response = dialog.run() | |
263 | if response == 42: | |
264 | error = event.error_name | |
265 | event.callback(error, *event.exception_objects) | |
266 | dialog.destroy() | |
285 | elif event.type() == 3132: # error | |
286 | self.window.emit("normal_error", event.text) | |
287 | ||
288 | elif event.type() == 3134: # important error, uncaught exception | |
289 | self.window.prepare_important_error(event) | |
290 | self.window.emit("important_error") | |
267 | 291 | |
268 | 292 | def update_counts(self): |
293 | """Update the counts for host, services and vulns""" | |
269 | 294 | host_count = self.model_controller.getHostsCount() |
270 | 295 | service_count = self.model_controller.getServicesCount() |
271 | 296 | vuln_count = self.model_controller.getVulnsCount() |
272 | 297 | return host_count, service_count, vuln_count |
298 | ||
299 | def on_open_report_button(self, action, param): | |
300 | """What happens when the user clicks the open report button. | |
301 | A dialog will present itself with a combobox to select a plugin. | |
302 | Then a file chooser to select a report. The report will be processed | |
303 | with the selected plugin. | |
304 | """ | |
305 | ||
306 | def select_plugin(): | |
307 | """Creates a simple dialog with a combo box to select a plugin""" | |
308 | plugins_id = [_id for _id in self.plugin_manager.getPlugins()] | |
309 | plugins_id = sorted(plugins_id) | |
310 | dialog = Gtk.Dialog("Select plugin", self.window, 0) | |
311 | ||
312 | combo_box = Gtk.ComboBoxText() | |
313 | for plugin_id in plugins_id: | |
314 | combo_box.append_text(plugin_id) | |
315 | combo_box.show() | |
316 | ||
317 | dialog.vbox.pack_start(combo_box, True, True, 10) | |
318 | ||
319 | dialog.add_button("Cancel", Gtk.ResponseType.DELETE_EVENT) | |
320 | dialog.add_button("OK", Gtk.ResponseType.ACCEPT) | |
321 | ||
322 | response = dialog.run() | |
323 | selected = combo_box.get_active_text() | |
324 | ||
325 | dialog.destroy() | |
326 | return response, selected | |
327 | ||
328 | def on_file_selected(plugin_id, report): | |
329 | """Send the plugin_id and the report file to be processed""" | |
330 | self.report_manager.sendReportToPluginById(plugin_id, report) | |
331 | ||
332 | plugin_response, plugin_id = select_plugin() | |
333 | ||
334 | while plugin_response == Gtk.ResponseType.ACCEPT and plugin_id is None: | |
335 | # force user to select a plugin if he did not do it | |
336 | errorDialog(self.window, | |
337 | "Please select a plugin to parse your report!") | |
338 | plugin_response, plugin_id = select_plugin() | |
339 | else: | |
340 | if plugin_response == Gtk.ResponseType.ACCEPT: | |
341 | dialog = Gtk.FileChooserNative() | |
342 | dialog.set_title("Import a report") | |
343 | dialog.set_modal(True) | |
344 | dialog.set_transient_for(self.window) | |
345 | dialog.set_action(Gtk.FileChooserAction.OPEN) | |
346 | ||
347 | res = dialog.run() | |
348 | if res == Gtk.ResponseType.ACCEPT: | |
349 | on_file_selected(plugin_id, dialog.get_filename()) | |
350 | dialog.destroy() | |
273 | 351 | |
274 | 352 | def on_about(self, action, param): |
275 | 353 | """ Defines what happens when you press 'about' on the menu""" |
295 | 373 | self.window) |
296 | 374 | preference_window.show_all() |
297 | 375 | |
376 | def show_host_info(self, host_id): | |
377 | """Looks up the host selected in the HostSidebar by id and shows | |
378 | its information on the HostInfoDialog""" | |
379 | ||
380 | for host in self.all_hosts: | |
381 | if host_id == host.id: | |
382 | selected_host = host | |
383 | break | |
384 | ||
385 | info_window = HostInfoDialog(self.window, selected_host) | |
386 | info_window.show_all() | |
387 | ||
298 | 388 | def reloadWorkspaces(self): |
299 | """Used in conjunction with on_preferences: close workspace, | |
300 | resources the workspaces available, clears the sidebar of the old | |
301 | workspaces and injects all the new ones in there too""" | |
389 | """Close workspace, resources the workspaces available, | |
390 | clears the sidebar of the old workspaces and injects all the new ones | |
391 | in there too""" | |
302 | 392 | self.workspace_manager.closeWorkspace() |
303 | 393 | self.workspace_manager.resource() |
304 | self.sidebar.clearSidebar() | |
305 | self.sidebar.refreshSidebar() | |
394 | self.ws_sidebar.clearSidebar() | |
395 | self.ws_sidebar.refreshSidebar() | |
306 | 396 | |
307 | 397 | def on_pluginOptions(self, action, param): |
308 | 398 | """Defines what happens when you press "Plugins" on the menu""" |
314 | 404 | "Defines what happens when you press the 'new' button on the toolbar" |
315 | 405 | new_workspace_dialog = NewWorkspaceDialog(self.createWorkspace, |
316 | 406 | self.workspace_manager, |
317 | self.sidebar, self.window, | |
407 | self.ws_sidebar, self.window, | |
318 | 408 | title) |
319 | 409 | new_workspace_dialog.show_all() |
320 | 410 | |
328 | 418 | |
329 | 419 | def on_click_notifications(self, button): |
330 | 420 | """Defines what happens when the user clicks on the notifications |
331 | button.""" | |
421 | button: just show a silly window with a treeview containing | |
422 | all the notifications""" | |
332 | 423 | |
333 | 424 | notifications_view = Gtk.TreeView(self.notificationsModel) |
334 | 425 | renderer = Gtk.CellRendererText() |
340 | 431 | notifications_dialog.show_all() |
341 | 432 | |
342 | 433 | def on_click_conflicts(self, button=None): |
343 | """Doesn't use the button at all. Shows the conflict dialog""" | |
434 | """Doesn't use the button at all, there cause GTK likes it. | |
435 | Shows the conflict dialog. | |
436 | """ | |
344 | 437 | self.updateConflicts() |
345 | 438 | if self.conflicts: |
346 | 439 | dialog = ConflictsDialog(self.conflicts, |
357 | 450 | dialog.destroy() |
358 | 451 | |
359 | 452 | def delete_notifications(self): |
453 | """Clear the notifications model of all info, also send a signal | |
454 | to get the notification label to 0 on the main window's button | |
455 | """ | |
360 | 456 | self.notificationsModel.clear() |
361 | 457 | self.window.emit("clear_notifications") |
362 | 458 | |
363 | def changeWorkspace(self, selection): | |
364 | """Pretty much copy/pasted from QT3 GUI. | |
365 | Selection is actually used nowhere, but the connect function is | |
366 | Sidebar passes it as an argument so well there it is""" | |
367 | ||
368 | tree_model, treeiter = selection.get_selected() | |
369 | workspaceName = tree_model[treeiter][0] | |
370 | ||
371 | try: | |
372 | ws = super(GuiApp, self).openWorkspace(workspaceName) | |
373 | except Exception as e: | |
374 | model.guiapi.notification_center.showDialog(str(e)) | |
375 | ws = self.openDefaultWorkspace() | |
376 | workspace = ws.name | |
377 | CONF.setLastWorkspace(workspace) | |
378 | CONF.saveConfig() | |
379 | return ws | |
459 | def changeWorkspace(self, workspaceName): | |
460 | """Changes workspace in a separate thread. Emits a signal | |
461 | to present a 'Loading workspace' dialog while Faraday processes | |
462 | the change""" | |
463 | ||
464 | def background_process(): | |
465 | self.window.emit("loading_workspace", 'show') | |
466 | try: | |
467 | ws = super(GuiApp, self).openWorkspace(workspaceName) | |
468 | except Exception as e: | |
469 | model.guiapi.notification_center.showDialog(str(e)) | |
470 | ws = self.openDefaultWorkspace() | |
471 | ||
472 | workspace = ws.name | |
473 | CONF.setLastWorkspace(workspace) | |
474 | CONF.saveConfig() | |
475 | self.window.emit("loading_workspace", "destroy") | |
476 | ||
477 | return True | |
478 | ||
479 | thread = threading.Thread(target=background_process) | |
480 | thread.daemon = True | |
481 | thread.start() | |
380 | 482 | |
381 | 483 | def run(self, args): |
382 | 484 | """First method to run, as defined by FaradayUi. This method is |
13 | 13 | gi.require_version('Vte', '2.91') |
14 | 14 | |
15 | 15 | from gi.repository import GLib, Gio, Gtk, GObject, Gdk |
16 | from dialogs import ImportantErrorDialog | |
17 | from dialogs import errorDialog | |
16 | 18 | |
17 | 19 | CONF = getInstanceConfiguration() |
18 | 20 | |
40 | 42 | "new_notif": (GObject.SIGNAL_RUN_FIRST, None, ()), |
41 | 43 | "clear_notifications": (GObject.SIGNAL_RUN_FIRST, None, ()), |
42 | 44 | "update_ws_info": (GObject.SIGNAL_RUN_FIRST, None, (int, int, int, )), |
43 | "set_conflict_label": (GObject.SIGNAL_RUN_FIRST, None, (int, )) | |
45 | "set_conflict_label": (GObject.SIGNAL_RUN_FIRST, None, (int, )), | |
46 | "loading_workspace": (GObject.SIGNAL_RUN_FIRST, None, (str, )), | |
47 | "normal_error": (GObject.SIGNAL_RUN_FIRST, None, (str, )), | |
48 | "important_error": (GObject.SIGNAL_RUN_FIRST, None, ()), | |
44 | 49 | } |
45 | 50 | |
46 | 51 | def __init__(self, sidebar, terminal, console_log, statusbar, |
64 | 69 | |
65 | 70 | self.icons = CONF.getImagePath() + "icons/" |
66 | 71 | |
67 | # sets up the clipboard | |
68 | self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) | |
69 | self.selection_clipboard = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY) | |
70 | ||
71 | 72 | # Keep it in sync with the actual state. Deep dark GTK magic |
72 | 73 | self.connect("notify::is-maximized", |
73 | 74 | lambda obj, pspec: |
79 | 80 | self.topBox.pack_start(self.create_toolbar(), True, True, 0) |
80 | 81 | |
81 | 82 | # SIDEBAR BOX |
82 | search = self.sidebar.getSearchEntry() | |
83 | self.sidebarBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
84 | self.sidebarBox.pack_start(search, False, False, 0) | |
85 | self.sidebarBox.pack_start(self.sidebar.scrollableView, True, True, 0) | |
86 | self.sidebarBox.pack_start(self.sidebar.getButton(), False, False, 0) | |
83 | self.sidebarBox = self.sidebar.get_box() | |
87 | 84 | |
88 | 85 | # TERMINAL BOX |
89 | 86 | self.firstTerminalBox = self.terminalBox(self.terminal.getTerminal()) |
142 | 139 | """Defines the menu created when a user rightclicks on the |
143 | 140 | terminal eventbox""" |
144 | 141 | menu = Gtk.Menu() |
145 | copy = Gtk.MenuItem("Copy") | |
146 | paste = Gtk.MenuItem("Paste") | |
147 | menu.append(paste) | |
148 | menu.append(copy) | |
149 | ||
150 | # TODO: make accelerators for copy paste work. add accel for paste | |
151 | # accelgroup = Gtk.AccelGroup() | |
152 | # self.add_accel_group(accelgroup) | |
153 | # accellabel = Gtk.AccelLabel("Copy/Paste") | |
154 | # accellabel.set_hexpand(True) | |
155 | # copy.add_accelerator("activate", | |
156 | # accelgroup, | |
157 | # Gdk.keyval_from_name("c"), | |
158 | # Gdk.ModifierType.SHIFT_MASK | | |
159 | # Gdk.ModifierType.CONTROL_MASK, | |
160 | # Gtk.AccelFlags.VISIBLE) | |
161 | ||
162 | copy.connect("activate", self.copy_text) | |
163 | paste.connect("activate", self.paste_text) | |
164 | ||
165 | copy.show() | |
166 | paste.show() | |
142 | self.copy = Gtk.MenuItem("Copy") | |
143 | self.paste = Gtk.MenuItem("Paste") | |
144 | menu.append(self.paste) | |
145 | menu.append(self.copy) | |
146 | ||
147 | self.copy.connect("activate", self.copy_text) | |
148 | self.paste.connect("activate", self.paste_text) | |
149 | ||
150 | self.copy.show() | |
151 | self.paste.show() | |
167 | 152 | menu.popup(None, None, None, None, event.button, event.time) |
168 | 153 | |
169 | def copy_text(self, button): | |
170 | """What happens when the user copies text""" | |
171 | content = self.selection_clipboard.wait_for_text() | |
172 | self.clipboard.set_text(content, -1) | |
173 | ||
174 | def paste_text(self, button): | |
175 | """What happens when the user pastes text""" | |
154 | def copy_text(self, _): | |
155 | """When the user presses on the copy button on the menu...""" | |
156 | currentTerminal = self.getCurrentFocusedTerminal() | |
157 | currentTerminal.copy_clipboard() | |
158 | ||
159 | def paste_text(self, _): | |
160 | """When the user presses on the paste button on the menu...""" | |
176 | 161 | currentTerminal = self.getCurrentFocusedTerminal() |
177 | 162 | currentTerminal.paste_clipboard() |
178 | 163 | |
183 | 168 | def getCurrentFocusedTerminal(self): |
184 | 169 | """Returns the current focused terminal""" |
185 | 170 | |
186 | # the focused terminal is the only children of the notebook | |
187 | # thas has only children an event box that has as only children | |
188 | # the scrolled window that has as only children the | |
189 | # terminal. Yeah, I know. | |
171 | # the focused terminal is the child of the event box which is | |
172 | # the top widget of the focused tab. that event box has as only child | |
173 | # a box, which has as only child a scrolled window, which has as | |
174 | # only child the terminal. yeah. I know. | |
190 | 175 | |
191 | 176 | currentTab = self.getFocusedTab() |
192 | 177 | currentEventBox = self.notebook.get_children()[currentTab] |
195 | 180 | currentTerminal = currentScrolledWindow.get_child() |
196 | 181 | return currentTerminal |
197 | 182 | |
183 | def prepare_important_error(self, event): | |
184 | """Attaches an event to the class, so it can be used by the signal | |
185 | callbacks even if they cannot be passed directly. | |
186 | """ | |
187 | self.event = event | |
188 | ||
189 | def do_important_error(self): | |
190 | """Creates an importan error dialog with a callback to send | |
191 | the developers the error traceback. | |
192 | """ | |
193 | dialog_text = self.event.text | |
194 | dialog = ImportantErrorDialog(self, dialog_text) | |
195 | response = dialog.run() | |
196 | if response == 42: | |
197 | error = self.event.error_name | |
198 | event.callback(error, *self.event.exception_objects) | |
199 | dialog.destroy() | |
200 | ||
201 | def do_normal_error(self, dialog_text): | |
202 | """Just a simple, normal, ignorable error""" | |
203 | dialog = Gtk.MessageDialog(self, 0, | |
204 | Gtk.MessageType.ERROR, | |
205 | Gtk.ButtonsType.OK, | |
206 | dialog_text) | |
207 | dialog.run() | |
208 | dialog.destroy() | |
209 | ||
198 | 210 | def do_new_log(self, text): |
199 | 211 | """To be used on a new_log signal. Calls a method on log to append |
200 | 212 | to it""" |
209 | 221 | self.statusbar.inc_notif_button_label() |
210 | 222 | |
211 | 223 | def do_set_conflict_label(self, conflict_number): |
224 | """Sets the conflict label to the appropiate conflict_number""" | |
212 | 225 | self.statusbar.update_conflict_button_label(conflict_number) |
213 | 226 | |
214 | 227 | def do_update_ws_info(self, host_count, service_count, vuln_count): |
228 | """Sets the statusbar workspace info to the appropiate numbers""" | |
215 | 229 | self.statusbar.update_ws_info(host_count, service_count, vuln_count) |
230 | ||
231 | def do_loading_workspace(self, action): | |
232 | """Called by changeWorkspace on the application. Presents | |
233 | a silly loading dialog. | |
234 | Preconditions: show must have been called before destroy can be called | |
235 | """ | |
236 | def do_nothing(widget, event): | |
237 | """Do nothing. Well, technically, return True. | |
238 | ||
239 | Avoids the user to interact with the dialog in anyway, for example, | |
240 | via the Escape key. | |
241 | You'll have to wait for my dialog to exit by itself, cowboy. | |
242 | """ | |
243 | return True | |
244 | ||
245 | if action == "show": | |
246 | self.loading_dialog = Gtk.MessageDialog(self, 0, | |
247 | Gtk.MessageType.INFO, | |
248 | Gtk.ButtonsType.NONE, | |
249 | ("Loading workspace. \n" | |
250 | "Please wait.")) | |
251 | ||
252 | self.loading_dialog.set_modal(True) | |
253 | self.loading_dialog.connect("key_press_event", do_nothing) | |
254 | ||
255 | self.loading_dialog.show_all() | |
256 | if action == "destroy": | |
257 | self.loading_dialog.destroy() | |
258 | ||
216 | 259 | |
217 | 260 | def getLogConsole(self): |
218 | 261 | """Returns the LogConsole. Needed by the GUIHandler logger""" |
228 | 271 | |
229 | 272 | def refreshSidebar(self): |
230 | 273 | """Call the refresh method on sidebar. It will append new workspaces, |
231 | but it will *NOT* delete workspaces not found anymore in the current | |
232 | ws anymore""" | |
274 | but it will *NOT* delete workspaces not found in the current | |
275 | ws""" | |
233 | 276 | self.sidebar.refresh() |
234 | 277 | |
235 | 278 | def create_toolbar(self): |
236 | 279 | """ Creates toolbar with an open and new button, getting the icons |
237 | from the stock. The method by which it does this is deprecated, | |
238 | this could be improved""" | |
280 | from the stock. """ | |
239 | 281 | |
240 | 282 | toolbar = Gtk.Toolbar() |
241 | 283 | toolbar.set_hexpand(True) |
247 | 289 | new_terminal_icon = Gtk.Image.new_from_file(icons + "newshell.png") |
248 | 290 | preferences_icon = Gtk.Image.new_from_file(icons + "config.png") |
249 | 291 | toggle_log_icon = Gtk.Image.new_from_file(icons + "debug.png") |
292 | open_report_icon = Gtk.Image.new_from_file(icons + "FolderSteel-20.png") | |
250 | 293 | |
251 | 294 | new_terminal_button = Gtk.ToolButton.new(new_terminal_icon, None) |
252 | 295 | new_terminal_button.set_tooltip_text("Create a new tab") |
270 | 313 | toggle_log_button.connect("clicked", self.toggle_log) |
271 | 314 | toolbar.insert(toggle_log_button, 3) |
272 | 315 | |
316 | space = Gtk.ToolItem() | |
317 | space.set_expand(True) | |
318 | toolbar.insert(space, 4) | |
319 | ||
320 | open_report_button = Gtk.ToolButton.new(open_report_icon, None) | |
321 | open_report_button.set_tooltip_text("Import report") | |
322 | open_report_button.set_action_name('app.open_report') | |
323 | toolbar.insert(open_report_button, 5) | |
324 | ||
273 | 325 | return toolbar |
274 | 326 | |
275 | 327 | def new_tab(self, scrolled_window): |
283 | 335 | tab_number = self.tab_number |
284 | 336 | pageN = self.terminalBox(scrolled_window) |
285 | 337 | self.notebook.append_page(pageN, Gtk.Label(str(tab_number+1))) |
286 | self.show_all() | |
338 | self.notebook.show_all() | |
287 | 339 | |
288 | 340 | def delete_tab(self, button=None): |
289 | 341 | """Deletes the current tab or closes the window if tab is only tab""" |
290 | 342 | if self.tab_number == 0: |
291 | # the following confusing but its how gtks handles delete_event | |
343 | # the following is confusing but its how gtks handles delete_event | |
292 | 344 | # if user said YES to confirmation, do_delete_event returns False |
293 | 345 | if not self.do_delete_event(): |
294 | 346 | self.destroy() |
325 | 377 | if response == Gtk.ResponseType.YES: |
326 | 378 | return False # keep on going and destroy |
327 | 379 | else: |
328 | # user say you know what i don't want to exit | |
380 | # user said "you know what i don't want to exit" | |
329 | 381 | return True |
330 | 382 | |
331 | 383 | def on_terminal_exit(self, terminal, status): |
19 | 19 | |
20 | 20 | CONF = getInstanceConfiguration() |
21 | 21 | |
22 | """This could probably be made much better with just a little effort. | |
23 | It'd be probably a good idea to make a super class Dialog from which | |
24 | all the dialogs inherit from with the common methods used (particularly the | |
25 | OK and Cancel buttons). Good starting point if we continue on with the idea | |
26 | of using GTK. | |
27 | ||
28 | Update: so it seems like Gtk actually already provides a Gtk.Dialog class | |
29 | which would seem practical. All dialogs are already made and it is a | |
30 | convenience class only, but if there's need to add more, it's a good | |
31 | thing to know""" | |
32 | ||
33 | ||
34 | 22 | class PreferenceWindowDialog(Gtk.Window): |
35 | 23 | """Sets up a preference dialog with basically nothing more than a |
36 | 24 | label, a text entry to input your CouchDB IP and a couple of buttons. |
43 | 31 | self.set_modal(True) |
44 | 32 | self.set_size_request(400, 100) |
45 | 33 | self.set_type_hint(Gdk.WindowTypeHint.DIALOG) |
46 | self.connect("key_press_event", on_scape) | |
34 | self.connect("key_press_event", on_scape_destroy) | |
47 | 35 | self.set_transient_for(parent) |
48 | 36 | self.timeout_id = None |
49 | 37 | self.reloadWorkspaces = callback |
104 | 92 | self.set_type_hint(Gdk.WindowTypeHint.DIALOG) |
105 | 93 | self.set_transient_for(parent) |
106 | 94 | self.set_modal(True) |
107 | self.connect("key_press_event", on_scape) | |
95 | self.connect("key_press_event", on_scape_destroy) | |
108 | 96 | self.set_size_request(200, 200) |
109 | 97 | self.timeout_id = None |
110 | 98 | self.callback = callback |
196 | 184 | self.set_type_hint(Gdk.WindowTypeHint.DIALOG) |
197 | 185 | self.set_transient_for(parent) |
198 | 186 | self.set_modal(True) |
199 | self.connect("key_press_event", on_scape) | |
187 | self.connect("key_press_event", on_scape_destroy) | |
200 | 188 | self.set_size_request(800, 300) |
201 | 189 | |
202 | 190 | if plugin_manager is not None: |
393 | 381 | self.createAdecuatePluginSettingView(adecuateModel) |
394 | 382 | |
395 | 383 | |
384 | class HostInfoDialog(Gtk.Window): | |
385 | """Sets the blueprints for a simple host info window. It will display | |
386 | basic information in labels as well as interfaces/services in a treeview | |
387 | """ | |
388 | def __init__(self, parent, host): | |
389 | """Creates a window with the information about a given hosts. | |
390 | The parent is needed so the window can set transient for | |
391 | """ | |
392 | Gtk.Window.__init__(self, | |
393 | title="Host " + host.name + " information") | |
394 | self.set_transient_for(parent) | |
395 | self.set_size_request(1200, 500) | |
396 | self.set_modal(True) | |
397 | self.connect("key_press_event", on_scape_destroy) | |
398 | self.host = host | |
399 | ||
400 | self.specific_info = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
401 | self.specific_info_frame = self.create_scroll_frame( | |
402 | self.specific_info, | |
403 | "Service Information") | |
404 | ||
405 | self.specific_vuln_info = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
406 | self.specific_vuln_info_frame = self.create_scroll_frame( | |
407 | self.specific_vuln_info, | |
408 | "Vulnerability Information") | |
409 | ||
410 | basic_info_frame = self.create_basic_info_box(host) | |
411 | children_of_host_tree = self.create_display_tree_box(host) | |
412 | button = Gtk.Button.new_with_label("OK") | |
413 | button.connect("clicked", self.on_click_ok) | |
414 | ||
415 | main_box = Gtk.Box() | |
416 | ||
417 | info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
418 | info_box.pack_start(basic_info_frame, True, True, 10) | |
419 | info_box.pack_start(self.specific_info_frame, True, True, 10) | |
420 | info_box.pack_start(self.specific_vuln_info_frame, True, True, 10) | |
421 | info_box.pack_start(button, False, False, 10) | |
422 | ||
423 | main_tree_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
424 | main_tree_box.pack_start(children_of_host_tree, True, True, 10) | |
425 | main_tree_box.pack_start(Gtk.Box(), False, False, 10) | |
426 | ||
427 | vuln_list_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
428 | vuln_list_box.pack_start(self.create_vuln_tree_box(), True, True, 10) | |
429 | vuln_list_box.pack_start(Gtk.Box(), False, False, 10) | |
430 | ||
431 | main_box.pack_start(main_tree_box, False, False, 5) | |
432 | main_box.pack_start(vuln_list_box, False, False, 0) | |
433 | main_box.pack_start(info_box, True, True, 5) | |
434 | ||
435 | self.add(main_box) | |
436 | ||
437 | def create_scroll_frame(self, inner_box, label_str): | |
438 | """Create a scrollable frame. | |
439 | ||
440 | inner_box will be the scrollable frame content. | |
441 | label_str will be the scrollable frame title. | |
442 | ||
443 | Scrollable will be set to always show vertical scrollbars and will | |
444 | have disabled overlay scrolling | |
445 | """ | |
446 | label = Gtk.Label() | |
447 | label.set_markup("<big>" + label_str + "</big>") | |
448 | ||
449 | scroll_box = Gtk.ScrolledWindow(None, None) | |
450 | scroll_box.set_overlay_scrolling(False) | |
451 | scroll_box.set_policy(Gtk.PolicyType.AUTOMATIC, | |
452 | Gtk.PolicyType.ALWAYS) | |
453 | ||
454 | scroll_box.add(inner_box) | |
455 | ||
456 | frame = Gtk.Frame() | |
457 | frame.set_label_widget(label) | |
458 | frame.add(scroll_box) | |
459 | ||
460 | return frame | |
461 | ||
462 | def create_basic_info_box(self, host): | |
463 | """Creates a box where the basic information about the host | |
464 | lives in labels. It include names, OS, Owned status and vulnarability | |
465 | count. | |
466 | """ | |
467 | box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
468 | ||
469 | name_box = Gtk.Box() | |
470 | name_label = Gtk.Label() | |
471 | name_label.set_markup("<b>%s</b>: %s" % ("Name", host.getName())) | |
472 | name_label.set_selectable(True) | |
473 | name_box.pack_start(name_label, False, False, 5) | |
474 | ||
475 | os_box = Gtk.Box() | |
476 | os_label = Gtk.Label() | |
477 | os_label.set_markup("<b>%s</b>: %s" % ("OS", host.getOS())) | |
478 | os_label.set_selectable(True) | |
479 | os_box.pack_start(os_label, False, False, 5) | |
480 | ||
481 | owned_box = Gtk.Box() | |
482 | owned_label = Gtk.Label() | |
483 | owned_status = ("Yes" if host.isOwned() else "No") | |
484 | owned_label.set_markup("<b>%s: </b>%s" % ("Owned", owned_status)) | |
485 | owned_label.set_selectable(True) | |
486 | owned_box.pack_start(owned_label, False, False, 5) | |
487 | ||
488 | vulns_box = Gtk.Box() | |
489 | vulns_label = Gtk.Label() | |
490 | vulns_count = str(len(host.getVulns())) | |
491 | vulns_label.set_markup("<b>%s</b>: %s" % | |
492 | ("Vulnerabilities", vulns_count)) | |
493 | vulns_label.set_selectable(True) | |
494 | ||
495 | vulns_box.pack_start(vulns_label, False, False, 5) | |
496 | ||
497 | box.pack_start(name_box, False, True, 0) | |
498 | box.pack_start(os_box, False, True, 0) | |
499 | box.pack_start(owned_box, False, True, 0) | |
500 | box.pack_start(vulns_box, False, False, 0) | |
501 | ||
502 | basic_info_frame = self.create_scroll_frame(box, "Host Information") | |
503 | ||
504 | return basic_info_frame | |
505 | ||
506 | def create_vuln_tree_box(self): | |
507 | """Creates a simple view a vulnerabilities""" | |
508 | box = Gtk.Box() | |
509 | self.vuln_list = Gtk.TreeView() | |
510 | self.vuln_list.set_activate_on_single_click(True) | |
511 | renderer = Gtk.CellRendererText() | |
512 | column = Gtk.TreeViewColumn("Vulnerabilities", renderer, text=1) | |
513 | self.vuln_list.append_column(column) | |
514 | ||
515 | vuln_selection = self.vuln_list.get_selection() | |
516 | vuln_selection.connect("changed", self.on_vuln_selection) | |
517 | ||
518 | scrolled_view = Gtk.ScrolledWindow(None, None) | |
519 | scrolled_view.add(self.vuln_list) | |
520 | scrolled_view.set_min_content_width(250) | |
521 | box.pack_start(scrolled_view, True, True, 10) | |
522 | ||
523 | return box | |
524 | ||
525 | def create_display_tree_box(self, host): | |
526 | """Creates a model and a view for the interfaces/services of the host. | |
527 | Puts a scrolled window containing the view into a box and returns | |
528 | that. The models holds quite a bit of information. It has 11 columns | |
529 | holding the host ID and name as parent, all the information about | |
530 | the interfaces of that host and all the information about | |
531 | the services of those interfaces. | |
532 | """ | |
533 | ||
534 | box = Gtk.Box() | |
535 | interfaces = host.getAllInterfaces() | |
536 | # those are 15 strings | |
537 | model = Gtk.TreeStore(str, str, str, str, str, str, str, | |
538 | str, str, str, str, str, str) | |
539 | ||
540 | # GTK is very strict about how many columns the model has. | |
541 | # only the ID and the name are needed, but i still need to 'fill' | |
542 | # the other columns with dummy info | |
543 | ||
544 | display_str = host.getName() + " (" + str(len(host.getVulns())) + ")" | |
545 | host_iter = model.append(None, [host.getID(), host.getName(), | |
546 | "", "", "", "", "", "", | |
547 | "", "", "", "", display_str]) | |
548 | ||
549 | def lst_to_str(lst): | |
550 | """Convenient function to avoid this long line everywhere""" | |
551 | return ', '.join([str(word) for word in lst if word]) | |
552 | ||
553 | for interface in interfaces: | |
554 | ipv4_dic = interface.getIPv4() | |
555 | ipv6_dic = interface.getIPv6() | |
556 | vulns = interface.getVulns() | |
557 | display_str = interface.getName() + " (" + str(len(vulns)) + ")" | |
558 | ||
559 | tree_iter = model.append(host_iter, [interface.getID(), | |
560 | interface.getName(), | |
561 | interface.getDescription(), | |
562 | interface.getMAC(), | |
563 | ipv4_dic['mask'], | |
564 | ipv4_dic['gateway'], | |
565 | lst_to_str(ipv4_dic['DNS']), | |
566 | ipv4_dic['address'], | |
567 | ipv6_dic['prefix'], | |
568 | ipv6_dic['gateway'], | |
569 | lst_to_str(ipv6_dic['DNS']), | |
570 | ipv6_dic['address'], | |
571 | display_str]) | |
572 | ||
573 | services = interface.getAllServices() | |
574 | for service in services: | |
575 | # Same as with the host, empty strings are there | |
576 | # just to agree with the number of columns the model should | |
577 | # have | |
578 | vulns = service.getVulns() | |
579 | display_str = service.getName() + " (" + str(len(vulns)) + ")" | |
580 | model.append(tree_iter, [service.getID(), | |
581 | service.getName(), | |
582 | service.getDescription(), | |
583 | service.getProtocol(), | |
584 | service.getStatus(), | |
585 | lst_to_str(service.getPorts()), | |
586 | service.getVersion(), | |
587 | "Yes" if service.isOwned() else "No", | |
588 | "", "", "", "", display_str]) | |
589 | ||
590 | self.view = Gtk.TreeView(model) | |
591 | self.view.set_activate_on_single_click(True) | |
592 | ||
593 | renderer = Gtk.CellRendererText() | |
594 | column = Gtk.TreeViewColumn("Host/Interfaces/Services", | |
595 | renderer, text=12) | |
596 | ||
597 | self.view.append_column(column) | |
598 | selection = self.view.get_selection() | |
599 | selection.connect("changed", self.on_selection) | |
600 | ||
601 | scrolled_view = Gtk.ScrolledWindow(None, None) | |
602 | scrolled_view.add(self.view) | |
603 | scrolled_view.set_min_content_width(250) | |
604 | box.pack_start(scrolled_view, True, True, 10) | |
605 | ||
606 | return box | |
607 | ||
608 | def on_selection(self, tree_selection): | |
609 | """Defines what happens when the user clicks on a row. Shows | |
610 | the interface or service information according to what the user | |
611 | selected. Before calling the corresponding functions, will clear | |
612 | the current specific_info box. | |
613 | """ | |
614 | model, tree_iter = tree_selection.get_selected() | |
615 | iter_depth = model.iter_depth(tree_iter) | |
616 | selected = model[tree_iter] | |
617 | self.specific_info.foreach(self.reset_info) # delete what was there | |
618 | if iter_depth == 0: | |
619 | self.set_vuln_model(self.create_vuln_model(self.host)) | |
620 | elif iter_depth == 1: | |
621 | label = self.specific_info_frame.get_label_widget() | |
622 | label.set_markup("<big>Interface information</big>") | |
623 | self.show_interface_info(selected) | |
624 | interface = self.host.getInterface(selected[0]) | |
625 | self.set_vuln_model(self.create_vuln_model(interface)) | |
626 | elif iter_depth == 2: | |
627 | label = self.specific_info_frame.get_label_widget() | |
628 | label.set_markup("<big>Service information</big>") | |
629 | self.show_service_info(selected) | |
630 | parent_interface_iter = selected.get_parent() | |
631 | parent_interface_id = parent_interface_iter[0] | |
632 | parent_interface = self.host.getInterface(parent_interface_id) | |
633 | service = parent_interface.childs.get(selected[0], None) | |
634 | self.set_vuln_model(self.create_vuln_model(service)) | |
635 | ||
636 | def on_vuln_selection(self, vuln_selection): | |
637 | """Sets up the information necesarry to show the detailed information | |
638 | of the vulnerability. The try/except block is necesary 'cause GTK | |
639 | is silly and will emit the selection changed signal if the model | |
640 | changes even if nothing is selected""" | |
641 | ||
642 | model, vuln_iter = vuln_selection.get_selected() | |
643 | self.specific_vuln_info.foreach(self.reset_vuln_info) | |
644 | try: | |
645 | selected = model[vuln_iter] | |
646 | self.show_vuln_info(selected) | |
647 | except TypeError: | |
648 | return False | |
649 | ||
650 | def set_vuln_model(self, model): | |
651 | self.vuln_list.set_model(model) | |
652 | ||
653 | def create_vuln_model(self, obj): | |
654 | """Creates a model for the vulnerabilities of the selected object""" | |
655 | # those are 15 strings | |
656 | model = Gtk.ListStore(str, str, str, str, str, str, str, str, | |
657 | str, str, str, str, str, str, str) | |
658 | ||
659 | vulns = obj.getVulns() | |
660 | for vuln in vulns: | |
661 | _type = vuln.class_signature | |
662 | if _type == "Vulnerability": | |
663 | model.append([_type, vuln.getName(), vuln.getDescription(), | |
664 | vuln.getData(), vuln.getSeverity(), | |
665 | ', '.join(vuln.getRefs()), | |
666 | "", "", "", "", "", "", "", "", ""]) | |
667 | ||
668 | elif _type == "VulnerabilityWeb": | |
669 | model.append([_type, vuln.getName(), vuln.getDescription(), | |
670 | vuln.getData(), vuln.getSeverity(), | |
671 | ", ".join(vuln.getRefs()), vuln.getPath(), | |
672 | vuln.getWebsite(), vuln.getRequest(), | |
673 | vuln.getResponse(), vuln.getMethod(), | |
674 | vuln.getPname(), vuln.getParams(), | |
675 | vuln.getQuery(), vuln.getCategory()]) | |
676 | return model | |
677 | ||
678 | def show_interface_info(self, selected): | |
679 | """Creates labels for each of the properties of an interface. Appends | |
680 | them to the specific_info_box. | |
681 | """ | |
682 | for prop in enumerate(["Name: ", "Description: ", "MAC: ", | |
683 | "IPv4 Mask: ", "IPv4 Gateway: ", "IPv4 DNS: ", | |
684 | "IPv4 Address: ", "IPv6 Prefix: ", | |
685 | "IPv6 Gateway", "IPv6 DNS: ", | |
686 | "IPv6 Address: "], start=1): | |
687 | self.append_info_to_box(selected, prop, self.specific_info) | |
688 | ||
689 | def show_service_info(self, selected): | |
690 | """Creates labels for each of the properties of a service. Appends | |
691 | them to the specific_info_box. | |
692 | """ | |
693 | for prop in enumerate(["Name: ", "Description: ", "Protocol: ", | |
694 | "Status: ", "Port: ", "Version: ", | |
695 | "Is Owned?: "], start=1): | |
696 | self.append_info_to_box(selected, prop, self.specific_info) | |
697 | ||
698 | def show_vuln_info(self, selected): | |
699 | """Sends the information about the selected vuln to | |
700 | append_info_to_box. | |
701 | """ | |
702 | if selected[0] == "Vulnerability": | |
703 | for prop in enumerate(["Name: ", "Description: ", | |
704 | "Data: ", "Severity: ", | |
705 | "Refs: "], start=1): | |
706 | self.append_info_to_box(selected, prop, | |
707 | self.specific_vuln_info) | |
708 | if selected[0] == "VulnerabilityWeb": | |
709 | for prop in enumerate(["Name: ", "Description: ", | |
710 | "Data: ", "Severity: ", | |
711 | "Refs: ", "Path: ", | |
712 | "Website: ", "Request: ", | |
713 | "Response: ", "Method: ", | |
714 | "Pname: ", "Params: ", | |
715 | "Query: ", "Category: "], start=1): | |
716 | self.append_info_to_box(selected, prop, | |
717 | self.specific_vuln_info) | |
718 | ||
719 | def append_info_to_box(self, selected, prop, box): | |
720 | """Gets selected and prop and creates a label and appends | |
721 | them to the box parameter. | |
722 | """ | |
723 | prop_box = Gtk.Box() | |
724 | prop_label = Gtk.Label() | |
725 | prop_label.set_markup("<b> %s </b>" % (prop[1])) | |
726 | prop_label.set_selectable(True) | |
727 | value_label = Gtk.Label(selected[prop[0]]) | |
728 | value_label.set_selectable(True) | |
729 | prop_box.pack_start(prop_label, False, False, 0) | |
730 | prop_box.pack_start(value_label, False, False, 0) | |
731 | box.pack_start(prop_box, True, True, 0) | |
732 | box.show_all() | |
733 | ||
734 | def reset_info(self, widget): | |
735 | """Removes a widget from self.specific_info. Used to clear all | |
736 | the information before displaying new""" | |
737 | self.specific_info.remove(widget) | |
738 | ||
739 | def reset_vuln_info(self, widget): | |
740 | """Removes a widget from self.specific_vuln_info. Used to clear | |
741 | all the information before displaying new""" | |
742 | self.specific_vuln_info.remove(widget) | |
743 | ||
744 | def on_click_ok(self, button): | |
745 | self.destroy() | |
746 | ||
747 | ||
396 | 748 | class ConflictsDialog(Gtk.Window): |
397 | 749 | """Blueprints for a beautiful, colorful, gtk-esque conflicts |
398 | 750 | dialog. The user is confronted with two objects, one at the left, |
408 | 760 | self.set_transient_for(parent) |
409 | 761 | self.set_size_request(600, 400) |
410 | 762 | self.set_modal(True) |
411 | self.connect("key_press_event", on_scape) | |
763 | self.connect("key_press_event", on_scape_destroy) | |
412 | 764 | self.conflicts = conflicts |
413 | 765 | self.conflict_n = 0 |
414 | 766 | self.current_conflict = self.conflicts[self.conflict_n] |
493 | 845 | else: |
494 | 846 | self.destroy() |
495 | 847 | |
496 | except ValueError as e: | |
848 | except ValueError: | |
497 | 849 | dialog = Gtk.MessageDialog(self, 0, |
498 | 850 | Gtk.MessageType.INFO, |
499 | 851 | Gtk.ButtonsType.OK, |
569 | 921 | self.view.append_column(obj_column) |
570 | 922 | self.second_view = Gtk.TreeView(self.models[conflict_n]) |
571 | 923 | |
572 | ||
573 | 924 | self.second_view.append_column(prop2_column) |
574 | 925 | self.second_view.append_column(obj2_column) |
575 | 926 | |
576 | self.views_box.pack_start(self.view, True, True, 5) | |
577 | self.views_box.pack_start(self.second_view, True, True, 5) | |
927 | scrolled_view = Gtk.ScrolledWindow(None, None) | |
928 | second_scrolled_view = Gtk.ScrolledWindow(None, None) | |
929 | scrolled_view.add(self.view) | |
930 | second_scrolled_view.add(self.second_view) | |
931 | ||
932 | self.views_box.pack_start(scrolled_view, True, True, 5) | |
933 | self.views_box.pack_start(second_scrolled_view, True, True, 5) | |
578 | 934 | |
579 | 935 | else: |
580 | 936 | self.view.set_model(self.models[conflict_n]) |
853 | 1209 | self.set_transient_for(parent) |
854 | 1210 | self.set_size_request(400, 200) |
855 | 1211 | self.set_modal(True) |
856 | self.connect("key_press_event", on_scape) | |
1212 | self.connect("key_press_event", on_scape_destroy) | |
857 | 1213 | |
858 | 1214 | self.view = view |
859 | 1215 | self.destroy_notifications = callback |
931 | 1287 | |
932 | 1288 | |
933 | 1289 | class ImportantErrorDialog(Gtk.Dialog): |
1290 | """Blueprints for an uncaught exception handler. Presents the | |
1291 | traceback and has option to send error report to developers. | |
1292 | """ | |
934 | 1293 | |
935 | 1294 | def __init__(self, parent_window, error): |
936 | 1295 | Gtk.Dialog.__init__(self, "Error!", parent_window, 0) |
954 | 1313 | box.pack_start(scrolled_text, True, True, 0) |
955 | 1314 | self.show_all() |
956 | 1315 | |
957 | def on_scape(window, event): | |
1316 | ||
1317 | def on_scape_destroy(window, event): | |
958 | 1318 | """Silly function to destroy a window on escape key, to use |
959 | 1319 | with all the dialogs that should be Gtk.Dialogs but are Gtk.Windows |
960 | 1320 | or with windows that are too complex for gtk dialogs but should behave |
12 | 12 | gi.require_version('Gtk', '3.0') |
13 | 13 | gi.require_version('Vte', '2.91') |
14 | 14 | |
15 | from gi.repository import Gtk, Vte, GLib, Pango | |
15 | from gi.repository import Gtk, Gdk, Vte, GLib, Pango, GdkPixbuf | |
16 | 16 | |
17 | 17 | |
18 | 18 | class Terminal(Vte.Terminal): |
20 | 20 | corresponding host and port as specified by the CONF""" |
21 | 21 | def __init__(self, CONF): |
22 | 22 | super(Vte.Terminal, self).__init__() |
23 | ||
24 | self.pty = self.pty_new_sync(Vte.PtyFlags.DEFAULT, None) | |
25 | self.set_pty(self.pty) | |
26 | ||
27 | 23 | self.set_scrollback_lines(-1) |
28 | 24 | self.set_audible_bell(0) |
29 | ||
30 | self.faraday_directory = os.path.dirname(os.path.realpath(sys.argv[0])) | |
25 | self.connect("key_press_event", self.copy_or_paste) | |
31 | 26 | self.host, self.port = CONF.getApiRestfulConInfo() |
32 | self.faraday_exec = self.faraday_directory + "/faraday-terminal.zsh" | |
27 | ||
28 | faraday_directory = os.path.dirname(os.path.realpath('faraday.py')) | |
29 | self.faraday_exec = faraday_directory + "/faraday-terminal.zsh" | |
33 | 30 | |
34 | 31 | self.startFaraday() |
35 | 32 | |
52 | 49 | None, |
53 | 50 | None) |
54 | 51 | |
55 | ||
56 | class Sidebar(Gtk.Widget): | |
52 | def copy_or_paste(self, widget, event): | |
53 | """Decides if the Ctrl+Shift is pressed, in which case returns True. | |
54 | If Ctrl+Shift+C or Ctrl+Shift+V are pressed, copies or pastes, | |
55 | acordingly. Return necesary so it doesn't perform other action, | |
56 | like killing the process, on Ctrl+C. | |
57 | """ | |
58 | ||
59 | control_key = Gdk.ModifierType.CONTROL_MASK | |
60 | shift_key = Gdk.ModifierType.SHIFT_MASK | |
61 | if event.type == Gdk.EventType.KEY_PRESS: | |
62 | if event.state == shift_key | control_key: #both shift and control | |
63 | if event.keyval == 67: # that's the C key | |
64 | self.copy_clipboard() | |
65 | elif event.keyval == 86: # and that's the V key | |
66 | self.paste_clipboard() | |
67 | return True | |
68 | ||
69 | class Sidebar(Gtk.Notebook): | |
70 | """Defines the bigger sidebar in a notebook. One of its tabs will contain | |
71 | the workspace view, listing all the workspaces (WorkspaceSidebar) and the | |
72 | other will contain the information about hosts, services, and vulns | |
73 | (HostsSidebar) | |
74 | """ | |
75 | ||
76 | def __init__(self, workspace_sidebar, hosts_sidebar): | |
77 | """Attach to the notebok the workspace sidebar and the host_sidebar""" | |
78 | super(Gtk.Notebook, self).__init__() | |
79 | self.workspace_sidebar = workspace_sidebar | |
80 | self.hosts_sidebar = hosts_sidebar | |
81 | self.set_tab_pos(Gtk.PositionType.BOTTOM) | |
82 | ||
83 | self.append_page(self.workspace_sidebar, Gtk.Label("Workspaces")) | |
84 | self.append_page(self.hosts_sidebar, Gtk.Label("Hosts")) | |
85 | ||
86 | def get_box(self): | |
87 | box = Gtk.Box() | |
88 | box.pack_start(self, True, True, 0) | |
89 | return box | |
90 | ||
91 | class HostsSidebar(Gtk.Widget): | |
92 | """Defines the widget displayed when the user is in the "Hosts" tab of | |
93 | the Sidebar notebook. Will list all the host, and when clicking on one, | |
94 | will open a window with more information about it""" | |
95 | ||
96 | def __init__(self, open_dialog_callback, icons): | |
97 | """Initializes the HostsSidebar. Initialization by itself does | |
98 | almost nothing, the application will inmediatly call create_model | |
99 | with the last workspace and create_view with that model upon startup. | |
100 | """ | |
101 | ||
102 | super(Gtk.Widget, self).__init__() | |
103 | self.open_dialog_callback = open_dialog_callback | |
104 | self.current_model = None | |
105 | self.linux_icon = icons + "tux.png" | |
106 | self.windows_icon = icons + "windows.png" | |
107 | self.mac_icon = icons + "Apple.png" | |
108 | ||
109 | def create_model(self, hosts): | |
110 | """Creates a model for a lists of hosts. The model contians the | |
111 | host_id in the first column, the icon as a GdkPixbuf.Pixbuf() | |
112 | in the second column and a display_str with the host_name and the | |
113 | vulnerability count on the third column, like this: | |
114 | | HOST_ID | HOST_OS_PIXBUF | DISPLAY_STR | | |
115 | ================================================= | |
116 | | a923fd | LINUX_ICON | 192.168.1.2 (5) | | |
117 | """ | |
118 | def compute_vuln_count(host): | |
119 | """Returns the total vulnerability count for a given host""" | |
120 | vuln_count = 0 | |
121 | vuln_count += len(host.getVulns()) | |
122 | for interface in host.getAllInterfaces(): | |
123 | vuln_count += len(interface.getVulns()) | |
124 | for service in interface.getAllServices(): | |
125 | vuln_count += len(service.getVulns()) | |
126 | return str(vuln_count) | |
127 | ||
128 | def decide_icon(os): | |
129 | """Decides the correct Pixbuf icon for a OS. None if OS not | |
130 | found or not recognized. | |
131 | """ | |
132 | os = os.lower() | |
133 | if "linux" in os or "unix" in os: | |
134 | icon = GdkPixbuf.Pixbuf.new_from_file(self.linux_icon) | |
135 | elif "windows" in os: | |
136 | icon = GdkPixbuf.Pixbuf.new_from_file(self.windows_icon) | |
137 | elif "mac" in os: | |
138 | icon = GdkPixbuf.Pixbuf.new_from_file(self.mac_icon) | |
139 | else: | |
140 | icon = None | |
141 | return icon | |
142 | ||
143 | hosts_model = Gtk.ListStore(str, GdkPixbuf.Pixbuf(), str) | |
144 | for host in hosts: | |
145 | vuln_count = compute_vuln_count(host) | |
146 | display_str = host.name + " (" + vuln_count + ")" | |
147 | os = host.getOS() | |
148 | hosts_model.append([host.id, decide_icon(os), display_str]) | |
149 | self.current_model = hosts_model | |
150 | return hosts_model | |
151 | ||
152 | def create_view(self, model): | |
153 | """Creates a view displaying the third column of the given model as | |
154 | a text, and using an icon representing its second column. | |
155 | Will connect activation of a row with the on_click method | |
156 | """ | |
157 | ||
158 | def display_str(col, cell, model, _iter, user_data): | |
159 | cell.set_property('text', model.get_value(_iter, 2)) | |
160 | ||
161 | def set_icon(col, cell, model, _iter, user_data): | |
162 | icon = model.get_value(_iter, 1) | |
163 | if icon != "None": | |
164 | cell.set_property('pixbuf', | |
165 | GdkPixbuf.Pixbuf.new_from_file(icon)) | |
166 | ||
167 | self.view = Gtk.TreeView(model) | |
168 | self.view.set_activate_on_single_click(True) | |
169 | ||
170 | text_renderer = Gtk.CellRendererText() | |
171 | icon_renderer = Gtk.CellRendererPixbuf() | |
172 | ||
173 | column_hosts = Gtk.TreeViewColumn("Hosts", text_renderer, text=2) | |
174 | column_os = Gtk.TreeViewColumn("", icon_renderer, pixbuf=1) | |
175 | ||
176 | self.view.append_column(column_os) | |
177 | self.view.append_column(column_hosts) | |
178 | ||
179 | self.view.connect("row_activated", self.on_click) | |
180 | ||
181 | self.view.set_enable_search(True) | |
182 | self.view.set_search_column(2) | |
183 | ||
184 | return self.view | |
185 | ||
186 | def update(self, hosts): | |
187 | """Creates a new model from an updated list of hosts and adapts | |
188 | the view to reflect the changes""" | |
189 | model = self.create_model(hosts) | |
190 | self.update_view(model) | |
191 | ||
192 | def update_view(self, model): | |
193 | """Updates the view of the object with a new model""" | |
194 | self.view.set_model(model) | |
195 | ||
196 | def on_click(self, tree_view, path, column): | |
197 | """Sends the host_id of the clicked host back to the application""" | |
198 | tree_iter = self.current_model.get_iter(path) | |
199 | host_id = self.current_model[tree_iter][0] | |
200 | self.open_dialog_callback(host_id) | |
201 | ||
202 | def get_box(self): | |
203 | """Returns the box to be displayed in the appwindow""" | |
204 | box = Gtk.Box() | |
205 | scrolled_view = Gtk.ScrolledWindow(None, None) | |
206 | scrolled_view.add(self.view) | |
207 | box.pack_start(scrolled_view, True, True, 0) | |
208 | return box | |
209 | ||
210 | class WorkspaceSidebar(Gtk.Widget): | |
57 | 211 | """Defines the sidebar widget to be used by the AppWindow, passed as an |
58 | instance to itby the application. It only handles the view and the model, | |
212 | instance to the application. It only handles the view and the model, | |
59 | 213 | all the backend word is handled by the application via the callback""" |
60 | 214 | |
61 | 215 | def __init__(self, workspace_manager, callback_to_change_workspace, |
82 | 236 | self.scrollableView.set_min_content_width(160) |
83 | 237 | self.scrollableView.add(self.workspace_view) |
84 | 238 | |
239 | def get_box(self): | |
240 | box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
241 | box.pack_start(self.getSearchEntry(), False, False, 0) | |
242 | box.pack_start(self.getScrollableView(), True, True, 0) | |
243 | box.pack_start(self.getButton(), False, False, 0) | |
244 | return box | |
245 | ||
85 | 246 | def createSearchEntry(self): |
86 | 247 | """Returns a simple search entry""" |
87 | 248 | searchEntry = Gtk.Entry() |
92 | 253 | def getSearchEntry(self): |
93 | 254 | """Returns the search entry of the sidebar""" |
94 | 255 | return self.searchEntry |
256 | ||
257 | def getScrollableView(self): | |
258 | return self.scrollableView | |
95 | 259 | |
96 | 260 | def onSearchEnterKey(self, entry): |
97 | 261 | """When the users preses enter, if the workspace exists, |
102 | 266 | self.callbackCreateWs(title=entry.get_text()) |
103 | 267 | entry.set_text("") |
104 | 268 | else: |
105 | self.callbackChangeWs(selection) | |
269 | self.callbackChangeWs(self.getSelectedWsName()) | |
106 | 270 | ws_iter = self.getSelectedWsIter() |
107 | 271 | entry.set_text("") |
108 | 272 | self.selectWs(ws_iter) |
187 | 351 | select.select_path(path) |
188 | 352 | |
189 | 353 | # change the workspace to the newly selected |
190 | self.callbackChangeWs(self.getSelectedWs()) | |
354 | ||
355 | self.callbackChangeWs(self.getSelectedWsName()) | |
191 | 356 | |
192 | 357 | if event.button == 3: # 3 represents right click |
193 | 358 | menu = Gtk.Menu() |
194 | 359 | delete_item = Gtk.MenuItem("Delete") |
195 | 360 | menu.append(delete_item) |
196 | 361 | |
197 | # get the path of the item where the user clicked | |
198 | # then get its tree_iter. then get its name. then delete | |
362 | # get tree_iter from path. then get its name. then delete | |
199 | 363 | # that workspace |
200 | 364 | |
201 | 365 | tree_iter = self.workspace_model.get_iter(path) |
207 | 371 | menu.popup(None, None, None, None, event.button, event.time) |
208 | 372 | return True # prevents the click from selecting a workspace |
209 | 373 | |
374 | def change_label(self, new_label): | |
375 | self.sidebar_button.set_label(new_label) | |
376 | ||
377 | def restore_label(self): | |
378 | self.sidebar_button.set_label("Refresh workspaces") | |
379 | ||
210 | 380 | def addWorkspace(self, ws): |
211 | 381 | """Append ws workspace to the model""" |
212 | 382 | self.workspace_model.append([ws]) |
213 | 383 | |
214 | 384 | def getSelectedWs(self): |
215 | """Returns the name of the current selected workspace""" | |
385 | """Returns the selection of of the view. | |
386 | To retrieve the name, see getSelectedWsName""" | |
216 | 387 | selection = self.workspace_view.get_selection() |
217 | 388 | return selection |
218 | 389 | |
221 | 392 | selection = self.getSelectedWs() |
222 | 393 | _iter = selection.get_selected()[1] |
223 | 394 | return _iter |
395 | ||
396 | def getSelectedWsName(self): | |
397 | """Return the name of the selected workspace""" | |
398 | selection = self.getSelectedWs() | |
399 | tree_model, treeiter = selection.get_selected() | |
400 | workspaceName = tree_model[treeiter][0] | |
401 | return workspaceName | |
224 | 402 | |
225 | 403 | def selectWs(self, ws): |
226 | 404 | """Selects workspace ws in the list""" |
283 | 461 | return self.textBuffer |
284 | 462 | |
285 | 463 | def customEvent(self, text): |
286 | """Filters event so that only those with type 3131 get to the log""" | |
464 | """Filters event so that only those with type 3131 get to the log. | |
465 | Also split them, so we can add the correct formatting to the first | |
466 | part of the message""" | |
467 | ||
287 | 468 | text = text.split('-') |
288 | 469 | if text[0] == "INFO ": |
289 | 470 | self.update("[ " + text[0] + "]", self.bold) |
345 | 526 | self.notif_button = Gtk.Button.new() |
346 | 527 | self.set_default_notif_label() |
347 | 528 | self.notif_button.connect("clicked", notif_callback) |
529 | self.notif_button.connect("clicked", self.set_default_notif_label) | |
348 | 530 | |
349 | 531 | self.conflict_button = Gtk.Button.new() |
350 | 532 | self.set_default_conflict_label() |
379 | 561 | label.show() |
380 | 562 | self.conflict_button.add(label) |
381 | 563 | |
382 | def set_default_notif_label(self): | |
564 | def set_default_notif_label(self, button=None): | |
383 | 565 | """Creates the default label""" |
384 | 566 | self.notif_button_label_int = 0 |
385 | 567 | self.notif_button.set_label(self.notif_text + |
41 | 41 | |
42 | 42 | #Check if python2 is already installed |
43 | 43 | if ! which python2 > /dev/null; then |
44 | echo "[-] Please install Python2 or make sure it is in your path" | |
45 | exit 1 | |
44 | if ! which python2.7 > /dev/null; then | |
45 | echo "[-] Please install Python2 or make sure it is in your path" | |
46 | exit 1 | |
47 | fi | |
46 | 48 | fi |
47 | 49 | |
48 | 50 | echo "[+] Install $os $arch" |
43 | 43 | ) |
44 | 44 | return False |
45 | 45 | |
46 | return self._sendReport(parser.report_type, filename) | |
47 | ||
48 | def _sendReport(self, plugin_id, filename): | |
46 | return self.sendReport(parser.report_type, filename) | |
47 | ||
48 | def sendReport(self, plugin_id, filename): | |
49 | """Sends a report to the appropiate plugin specified by plugin_id""" | |
49 | 50 | getLogger(self).debug( |
50 | 51 | 'The file is %s, %s' % (filename, plugin_id)) |
51 | 52 | if not self.plugin_controller.processReport(plugin_id, filename): |
140 | 141 | if name in psettings: |
141 | 142 | if psettings[name]['settings']['Enable'] == "1": |
142 | 143 | self.processor.onlinePlugin(cmd) |
144 | ||
145 | def sendReportToPluginById(self, plugin_id, filename): | |
146 | """Sends a report to be processed by the specified plugin_id""" | |
147 | self.processor.sendReport(plugin_id, filename) | |
143 | 148 | |
144 | 149 | |
145 | 150 | class ReportParser(object): |