Imported Upstream version 1.0.22
Sophie Brun
7 years ago
8 | 8 | |
9 | 9 | New features in the latest update |
10 | 10 | ===================================== |
11 | ||
12 | Jul 1, 2016: | |
13 | --- | |
14 | * GTK is the default interface now. | |
15 | * Added new plugin : Ndiff. | |
16 | * Added new plugin : Netcat (Gnu netcat - OpenBSD netcat - Original netcat) | |
17 | * Added button to edit your host in the GTK interface. | |
18 | * Hosts sidebar now can be sorted by amout of vulnerabilities and OS. | |
19 | * Changes in installation: install.sh now installs only GTK, QT is considered deprecated. | |
20 | * Changes in installation: Faraday now runs with the last versions of Python modules. | |
21 | * Changes in installation: fixed names of packages in setup_server.sh | |
22 | * Usability: Enter key in GTK dialogs works as OK button | |
23 | * Improved handling of lost connection to CouchDB database | |
24 | * First steps towards deprecating Filesystem databases | |
25 | * Fixed a bug when workspace was changed | |
26 | * Fixed a bug with Import Reports Dialog in GTK GUI on OS X. | |
27 | * Fixed a bug with Ctrl+Shift+C and Ctrl+Shift+V in some desktops managers. | |
28 | * Fixed a bug with mapper of vulnerabilities. | |
11 | 29 | |
12 | 30 | Jun 13, 2016: |
13 | 31 | --- |
1 | 1 | <faraday> |
2 | 2 | |
3 | 3 | <appname>Faraday - Penetration Test IDE</appname> |
4 | <version>1.0.21</version> | |
4 | <version>1.0.22</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> |
59 | 59 | elif (change.getChangeType() != CHANGETYPE.UNKNOWN): |
60 | 60 | model.guiapi.notification_center.editHost(host) |
61 | 61 | |
62 | def manageConnectionLost(self): | |
63 | """All it does is send a notification to the notification center""" | |
64 | model.guiapi.notification_center.CouchDBConnectionProblem() | |
65 | ||
62 | 66 | def watch(self, mapper, dbConnector): |
63 | 67 | self.mapper_manager = mapper |
64 | 68 | self.dbConnector = dbConnector |
65 | 69 | self.changesWatcher = ChangeWatcher(dbConnector.waitForDBChange) |
66 | 70 | dbConnector.setChangesCallback(self.loadChange) |
71 | dbConnector.setCouchExceptionCallback(self.manageConnectionLost) | |
67 | 72 | self.changesWatcher.start() |
68 | 73 | |
69 | 74 | def unwatch(self): |
139 | 139 | help="Path to the valid CouchDB certificate") |
140 | 140 | |
141 | 141 | parser.add_argument('--gui', action="store", dest="gui", |
142 | default="qt3", | |
142 | default="gtk", | |
143 | 143 | help="Select interface to start faraday. Supported values are " |
144 | "qt3 (deprecated), gtk and 'no' (no GUI at all). Defaults to qt3") | |
144 | "qt3 (deprecated), gtk and 'no' (no GUI at all). Defaults to GTK") | |
145 | 145 | |
146 | 146 | parser.add_argument('--cli', action="store_true", |
147 | 147 | dest="cli", |
220 | 220 | if not line.find('#'): |
221 | 221 | break |
222 | 222 | else: |
223 | modules.append([line[:line.index('=')], (line[line.index('=')+2:]).strip()]) | |
223 | modules.append(line.strip('\n')) | |
224 | 224 | f.close() |
225 | 225 | pip_dist = [dist.project_name.lower() for dist in pip.get_installed_distributions()] |
226 | ||
227 | 226 | for module in modules: |
228 | if module[0].lower() not in pip_dist: | |
227 | if module.lower() not in pip_dist: | |
229 | 228 | try: |
230 | __import__(module[0]) | |
229 | __import__(module) | |
231 | 230 | except ImportError: |
232 | 231 | if query_user_bool("Missing module %s." |
233 | " Do you wish to install it?" % module[0]): | |
234 | pip.main(['install', "%s==%s" % | |
235 | (module[0], module[1]), '--user']) | |
232 | " Do you wish to install it?" % module): | |
233 | pip.main(['install', "%s" % | |
234 | module, '--user']) | |
236 | 235 | |
237 | 236 | else: |
238 | 237 | return False |
455 | 454 | shutil.copytree(FARADAY_BASE_IMAGES, FARADAY_USER_IMAGES) |
456 | 455 | |
457 | 456 | |
458 | def checkConfiguration(): | |
457 | def checkConfiguration(gui_type): | |
459 | 458 | """Checks if the environment is ready to run Faraday. |
460 | 459 | |
461 | 460 | Checks different environment requirements and sets them before starting |
462 | 461 | Faraday. This includes checking for plugin folders, libraries, QT |
463 | 462 | configuration and ZSH integration. |
464 | 463 | """ |
465 | ||
466 | 464 | logger.info("Checking configuration.") |
467 | 465 | logger.info("Setting up plugins.") |
468 | 466 | setupPlugins(args.dev_mode) |
469 | logger.info("Setting up Qt configuration.") | |
470 | setupQtrc() | |
467 | if gui_type == "qt3": | |
468 | logger.info("Setting up Qt configuration.") | |
469 | setupQtrc() | |
471 | 470 | logger.info("Setting up ZSH integration.") |
472 | 471 | setupZSH() |
473 | 472 | logger.info("Setting up user configuration.") |
474 | 473 | setupXMLConfig() |
475 | 474 | logger.info("Setting up libraries.") |
476 | 475 | setupLibs() |
477 | logger.info("Setting up icons for QT interface.") | |
478 | setupImages() | |
476 | if gui_type == "qt3": | |
477 | logger.info("Setting up icons for QT interface.") | |
478 | setupImages() | |
479 | 479 | |
480 | 480 | |
481 | 481 | def setupFolders(folderlist): |
617 | 617 | Main function for launcher. |
618 | 618 | |
619 | 619 | """ |
620 | ||
621 | 620 | os.chdir(FARADAY_BASE) |
622 | 621 | |
623 | 622 | init() |
626 | 625 | logger.info("Dependencies met.") |
627 | 626 | if args.cert_path: |
628 | 627 | os.environ[REQUESTS_CA_BUNDLE_VAR] = args.cert_path |
629 | checkConfiguration() | |
628 | checkConfiguration(args.gui) | |
630 | 629 | setConf() |
631 | 630 | checkCouchUrl() |
632 | 631 | checkVersion() |
29 | 29 | EDITHOST = 4102 |
30 | 30 | CHANGEFROMINSTANCE = 5100 |
31 | 31 | UPDATEMODEL_ID = 54321 |
32 | CONNECTION_REFUSED = 42424 | |
32 | 33 | |
33 | 34 | |
34 | 35 | class CustomEvent(object): |
72 | 73 | if error_name is not None: |
73 | 74 | self.error_name = error_name |
74 | 75 | |
76 | # this is probably a bad name for the class | |
77 | # maybe ConnectionRefusedCustomEven would've been better | |
78 | class ShowExceptionConnectionRefusedCustomEvent(CustomEvent): | |
79 | def __init__(self, problem=None): | |
80 | CustomEvent.__init__(self, CONNECTION_REFUSED) | |
81 | self.problem = problem | |
75 | 82 | |
76 | 83 | |
77 | 84 | class RenameHostsRootCustomEvent(CustomEvent): |
41 | 41 | from gui.gui_app import FaradayUi |
42 | 42 | from config.configuration import getInstanceConfiguration |
43 | 43 | from utils.logs import getLogger |
44 | from persistence.persistence_managers import CouchDbManager | |
44 | 45 | from appwindow import AppWindow |
45 | 46 | |
46 | 47 | from dialogs import PreferenceWindowDialog |
49 | 50 | from dialogs import NotificationsDialog |
50 | 51 | from dialogs import aboutDialog |
51 | 52 | from dialogs import helpDialog |
52 | from dialogs import ImportantErrorDialog | |
53 | 53 | from dialogs import ConflictsDialog |
54 | 54 | from dialogs import HostInfoDialog |
55 | 55 | from dialogs import errorDialog |
56 | from dialogs import ImportantErrorDialog | |
56 | 57 | |
57 | 58 | from mainwidgets import Sidebar |
58 | 59 | from mainwidgets import WorkspaceSidebar |
63 | 64 | |
64 | 65 | from gui.loghandler import GUIHandler |
65 | 66 | from utils.logs import addHandler |
67 | from utils.common import checkSSL | |
66 | 68 | |
67 | 69 | CONF = getInstanceConfiguration() |
68 | 70 | |
112 | 114 | def updateHosts(self): |
113 | 115 | """Reassings the value of self.all_hosts to a current one to |
114 | 116 | catch workspace changes, new hosts added via plugins or any other |
115 | external interference with out host list""" | |
117 | external interference with our host list""" | |
116 | 118 | self.all_hosts = self.model_controller.getAllHosts() |
119 | return self.all_hosts | |
117 | 120 | |
118 | 121 | def createWorkspace(self, name, description="", w_type=""): |
119 | 122 | """Pretty much copy/pasted from the QT3 GUI. |
143 | 146 | |
144 | 147 | return status |
145 | 148 | |
146 | def removeWorkspace(self, button, ws_name): | |
149 | def remove_workspace(self, button, ws_name): | |
147 | 150 | """Removes a workspace. If the workspace to be deleted is the one |
148 | 151 | selected, it moves you first to the default. The clears and refreshes |
149 | 152 | sidebar""" |
155 | 158 | self.ws_sidebar.clearSidebar() |
156 | 159 | self.ws_sidebar.refreshSidebar() |
157 | 160 | |
161 | def is_workspace_couch(self, workspace_name): | |
162 | """Return if the workspace named workspace_name is associated to a | |
163 | CouchDB. | |
164 | """ | |
165 | type_ = self.workspace_manager.getWorkspaceType(workspace_name) | |
166 | if type_ == "CouchDB": | |
167 | is_couch = True | |
168 | else: | |
169 | is_couch = False | |
170 | return is_couch | |
171 | ||
172 | def get_active_workspace(self): | |
173 | """Return the currently active workspace""" | |
174 | return self.workspace_manager.getActiveWorkspace() | |
175 | ||
158 | 176 | def do_startup(self): |
159 | 177 | """ |
160 | 178 | GTK calls this method after Gtk.Application.run() |
166 | 184 | Gtk.Application.do_startup(self) # deep GTK magic |
167 | 185 | |
168 | 186 | self.ws_sidebar = WorkspaceSidebar(self.workspace_manager, |
169 | self.changeWorkspace, | |
170 | self.removeWorkspace, | |
187 | self.change_workspace, | |
188 | self.remove_workspace, | |
171 | 189 | self.on_new_button, |
172 | 190 | CONF.getLastWorkspace()) |
173 | 191 | |
239 | 257 | # Windows are associated with the application |
240 | 258 | # when the last one is closed the application shuts down |
241 | 259 | self.window = AppWindow(self.sidebar, |
260 | self.ws_sidebar, | |
261 | self.hosts_sidebar, | |
242 | 262 | self.terminal, |
243 | 263 | self.console_log, |
244 | 264 | self.statusbar, |
258 | 278 | model.guiapi.notification_center.registerWidget(self.window) |
259 | 279 | |
260 | 280 | def postEvent(self, receiver, event): |
261 | """Handles the events from gui/customevents.""" | |
281 | """Handles the events from gui/customevents. | |
282 | ||
283 | DO NOT, AND I REPEAT, DO NOT REDRAW *ANYTHING* FROM THE GUI | |
284 | FROM HERE. If you must do it, you should to it via the emit method | |
285 | to the appwindow or maybe using Glib.idle_add, a misterious function | |
286 | with outdate documentation.""" | |
287 | ||
262 | 288 | if receiver is None: |
263 | 289 | receiver = self.getMainWindow() |
264 | 290 | |
277 | 303 | |
278 | 304 | elif event.type() == 4100 or event.type() == 3140: # newinfo or changews |
279 | 305 | host_count, service_count, vuln_count = self.update_counts() |
280 | self.updateHosts() | |
281 | self.hosts_sidebar.update(self.all_hosts) | |
282 | receiver.emit("update_ws_info", host_count, | |
283 | service_count, vuln_count) | |
306 | self.window.receive_hosts(self.updateHosts()) | |
307 | receiver.emit("update_hosts_sidebar") | |
308 | receiver.emit("update_ws_info", host_count, service_count, vuln_count) | |
284 | 309 | |
285 | 310 | elif event.type() == 3132: # error |
286 | 311 | self.window.emit("normal_error", event.text) |
287 | 312 | |
288 | 313 | elif event.type() == 3134: # important error, uncaught exception |
289 | self.window.prepare_important_error(event) | |
314 | GObject.idle_add(self.window.prepare_important_error, event) | |
290 | 315 | self.window.emit("important_error") |
316 | ||
317 | elif event.type() == 42424: # lost connection to couch db | |
318 | GObject.idle_add(self.window.prepare_important_error, event, | |
319 | self.handle_connection_lost) | |
320 | ||
321 | self.window.emit("lost_db_connection", event.problem) | |
322 | self.change_to_default_ws_on_connection_lost() | |
323 | ||
324 | def change_to_default_ws_on_connection_lost(self): | |
325 | """Reloads the workspace and opens the default ws""" | |
326 | ws = self.openDefaultWorkspace() | |
327 | self.reloadWorkspaces() | |
328 | CONF.setLastWorkspace(ws.name) | |
329 | CONF.saveConfig() | |
330 | ||
331 | def connect_to_couch(self, couch_uri): | |
332 | """Tries to connect to a CouchDB on a specified Couch URI. | |
333 | Returns the success status of the operation, False for not successful, | |
334 | True for successful | |
335 | """ | |
336 | ||
337 | if not CouchDbManager.testCouch(couch_uri): | |
338 | errorDialog(self.window, "Could not connect to CouchDB.", | |
339 | ("Are you sure it is running and that you can " | |
340 | "connect to it? \n Make sure your username and " | |
341 | "password are still valid.")) | |
342 | success = False | |
343 | elif couch_uri.startswith("https://"): | |
344 | if not checkSSL(couch_uri): | |
345 | errorDialog(self.window, | |
346 | "The SSL certificate validation has failed") | |
347 | success = False | |
348 | else: | |
349 | CONF.setCouchUri(couch_uri) | |
350 | CONF.saveConfig() | |
351 | self.reloadWorkspaces() | |
352 | success = True | |
353 | return success | |
354 | ||
355 | def handle_connection_lost(self, button=None, dialog=None): | |
356 | """Tries to connect to Couch using the same URI""" | |
357 | couch_uri = CONF.getCouchURI() | |
358 | if self.connect_to_couch(couch_uri): | |
359 | if dialog is not None: | |
360 | dialog.destroy() | |
361 | reconnected = True | |
362 | else: | |
363 | reconnected = False | |
364 | return reconnected | |
291 | 365 | |
292 | 366 | def update_counts(self): |
293 | 367 | """Update the counts for host, services and vulns""" |
310 | 384 | dialog = Gtk.Dialog("Select plugin", self.window, 0) |
311 | 385 | |
312 | 386 | combo_box = Gtk.ComboBoxText() |
387 | combo_box.set_wrap_width(3) | |
313 | 388 | for plugin_id in plugins_id: |
314 | 389 | combo_box.append_text(plugin_id) |
315 | 390 | combo_box.show() |
316 | 391 | |
317 | dialog.vbox.pack_start(combo_box, True, True, 10) | |
392 | dialog.vbox.pack_start(combo_box, False, True, 10) | |
318 | 393 | |
319 | 394 | dialog.add_button("Cancel", Gtk.ResponseType.DELETE_EVENT) |
320 | 395 | dialog.add_button("OK", Gtk.ResponseType.ACCEPT) |
338 | 413 | plugin_response, plugin_id = select_plugin() |
339 | 414 | else: |
340 | 415 | if plugin_response == Gtk.ResponseType.ACCEPT: |
341 | dialog = Gtk.FileChooserNative() | |
342 | dialog.set_title("Import a report") | |
416 | dialog = Gtk.FileChooserDialog(title="Import a report", | |
417 | parent=self.window, | |
418 | action=Gtk.FileChooserAction.OPEN, | |
419 | buttons=("Open", Gtk.ResponseType.ACCEPT, | |
420 | "Cancel", Gtk.ResponseType.CANCEL) | |
421 | ) | |
343 | 422 | dialog.set_modal(True) |
344 | dialog.set_transient_for(self.window) | |
345 | dialog.set_action(Gtk.FileChooserAction.OPEN) | |
346 | 423 | |
347 | 424 | res = dialog.run() |
348 | 425 | if res == Gtk.ResponseType.ACCEPT: |
351 | 428 | |
352 | 429 | def on_about(self, action, param): |
353 | 430 | """ Defines what happens when you press 'about' on the menu""" |
354 | ||
355 | 431 | about_dialog = aboutDialog(self.window) |
356 | 432 | about_dialog.run() |
357 | 433 | about_dialog.destroy() |
358 | 434 | |
359 | 435 | def on_help(self, action, param): |
360 | 436 | """Defines what happens when user press 'help' on the menu""" |
361 | ||
362 | 437 | help_dialog = helpDialog(self.window) |
363 | 438 | help_dialog.run() |
364 | 439 | help_dialog.destroy() |
370 | 445 | new workspaces available""" |
371 | 446 | |
372 | 447 | preference_window = PreferenceWindowDialog(self.reloadWorkspaces, |
448 | self.connect_to_couch, | |
373 | 449 | self.window) |
374 | 450 | preference_window.show_all() |
375 | 451 | |
376 | 452 | def show_host_info(self, host_id): |
377 | 453 | """Looks up the host selected in the HostSidebar by id and shows |
378 | 454 | its information on the HostInfoDialog""" |
455 | current_ws_name = self.get_active_workspace().name | |
456 | is_ws_couch = self.is_workspace_couch(current_ws_name) | |
379 | 457 | |
380 | 458 | for host in self.all_hosts: |
381 | 459 | if host_id == host.id: |
382 | 460 | selected_host = host |
383 | 461 | break |
384 | 462 | |
385 | info_window = HostInfoDialog(self.window, selected_host) | |
463 | info_window = HostInfoDialog(self.window, current_ws_name, | |
464 | is_ws_couch, selected_host) | |
386 | 465 | info_window.show_all() |
387 | 466 | |
388 | 467 | def reloadWorkspaces(self): |
413 | 492 | instance of the Terminal and tells the window to add it as a new tab |
414 | 493 | for the notebook""" |
415 | 494 | new_terminal = Terminal(CONF) |
416 | the_new_terminal = new_terminal.getTerminal() | |
417 | AppWindow.new_tab(self.window, the_new_terminal) | |
495 | terminal_scrolled = new_terminal.getTerminal() | |
496 | self.window.new_tab(terminal_scrolled) | |
418 | 497 | |
419 | 498 | def on_click_notifications(self, button): |
420 | 499 | """Defines what happens when the user clicks on the notifications |
456 | 535 | self.notificationsModel.clear() |
457 | 536 | self.window.emit("clear_notifications") |
458 | 537 | |
459 | def changeWorkspace(self, workspaceName): | |
538 | def change_workspace(self, workspaceName): | |
460 | 539 | """Changes workspace in a separate thread. Emits a signal |
461 | 540 | to present a 'Loading workspace' dialog while Faraday processes |
462 | 541 | the change""" |
463 | 542 | |
464 | 543 | def background_process(): |
544 | """Change workspace. This function runs on a separated thread | |
545 | created by the parent function. DO NOT call any Gtk methods | |
546 | withing its scope, except by emiting signals to the window | |
547 | """ | |
465 | 548 | self.window.emit("loading_workspace", 'show') |
466 | 549 | try: |
467 | 550 | ws = super(GuiApp, self).openWorkspace(workspaceName) |
551 | self.window.emit("loading_workspace", "destroy") | |
468 | 552 | except Exception as e: |
469 | 553 | model.guiapi.notification_center.showDialog(str(e)) |
470 | 554 | ws = self.openDefaultWorkspace() |
555 | self.window.emit("loading_workspace", "destroy") | |
471 | 556 | |
472 | 557 | workspace = ws.name |
473 | 558 | CONF.setLastWorkspace(workspace) |
474 | 559 | CONF.saveConfig() |
475 | self.window.emit("loading_workspace", "destroy") | |
476 | 560 | |
477 | 561 | return True |
478 | 562 |
44 | 44 | "update_ws_info": (GObject.SIGNAL_RUN_FIRST, None, (int, int, int, )), |
45 | 45 | "set_conflict_label": (GObject.SIGNAL_RUN_FIRST, None, (int, )), |
46 | 46 | "loading_workspace": (GObject.SIGNAL_RUN_FIRST, None, (str, )), |
47 | "lost_db_connection": (GObject.SIGNAL_RUN_FIRST, None, (str,)), | |
48 | "update_hosts_sidebar": (GObject.SIGNAL_RUN_FIRST, None, ()), | |
47 | 49 | "normal_error": (GObject.SIGNAL_RUN_FIRST, None, (str, )), |
48 | 50 | "important_error": (GObject.SIGNAL_RUN_FIRST, None, ()), |
49 | 51 | } |
50 | 52 | |
51 | def __init__(self, sidebar, terminal, console_log, statusbar, | |
52 | *args, **kwargs): | |
53 | def __init__(self, sidebar, ws_sidebar, hosts_sidebar, terminal, | |
54 | console_log, statusbar, *args, **kwargs): | |
53 | 55 | super(Gtk.ApplicationWindow, self).__init__(*args, **kwargs) |
54 | 56 | |
55 | 57 | # This will be in the windows group and have the "win" prefix |
61 | 63 | self.maximize() |
62 | 64 | |
63 | 65 | self.sidebar = sidebar |
66 | self.ws_sidebar = ws_sidebar | |
67 | self.hosts_sidebar = hosts_sidebar | |
64 | 68 | self.terminal = terminal |
65 | 69 | self.log = console_log |
66 | 70 | self.statusbar = statusbar |
124 | 128 | self.tab_number = 0 # 0 indexed, even when it shows 1 to the user |
125 | 129 | |
126 | 130 | self.show_all() |
131 | ||
132 | def receive_hosts(self, hosts): | |
133 | """Attaches the hosts to an object value, so it can be used by | |
134 | do_update_hosts_sidebar, a signal. GTK won't alow anything | |
135 | more than primitive names to be passed on by signals""" | |
136 | self.current_hosts = hosts | |
137 | ||
138 | def do_update_hosts_sidebar(self): | |
139 | self.hosts_sidebar.update(self.current_hosts) | |
127 | 140 | |
128 | 141 | def terminalBox(self, terminal): |
129 | 142 | """Given a terminal, creates an EventBox for the Box that has as a |
180 | 193 | currentTerminal = currentScrolledWindow.get_child() |
181 | 194 | return currentTerminal |
182 | 195 | |
183 | def prepare_important_error(self, event): | |
196 | def prepare_important_error(self, event, *callbacks): | |
184 | 197 | """Attaches an event to the class, so it can be used by the signal |
185 | 198 | callbacks even if they cannot be passed directly. |
186 | 199 | """ |
187 | 200 | self.event = event |
201 | self.error_callbacks = callbacks | |
188 | 202 | |
189 | 203 | def do_important_error(self): |
190 | 204 | """Creates an importan error dialog with a callback to send |
195 | 209 | response = dialog.run() |
196 | 210 | if response == 42: |
197 | 211 | error = self.event.error_name |
198 | event.callback(error, *self.event.exception_objects) | |
212 | self.event.callback(error, *self.event.exception_objects) | |
199 | 213 | dialog.destroy() |
200 | 214 | |
201 | 215 | def do_normal_error(self, dialog_text): |
207 | 221 | dialog.run() |
208 | 222 | dialog.destroy() |
209 | 223 | |
224 | def do_lost_db_connection(self, explanatory_message): | |
225 | """Creates a simple dialog with an error message to inform the user | |
226 | some kind of problem has happened and the connection was lost. | |
227 | Uses the first callback on self.error_callbacks, which should | |
228 | point to the application's handle_connection_lost method. | |
229 | """ | |
230 | ||
231 | def destroy_dialog(button=None): | |
232 | """Necessary 'cause button.connect method passes the button | |
233 | as a paramether even when I don't need it. | |
234 | """ | |
235 | dialog.destroy() | |
236 | ||
237 | handle_connection_lost = self.error_callbacks[0] | |
238 | if explanatory_message: | |
239 | explanation = "\n The specific error was: " + explanatory_message | |
240 | else: | |
241 | explanation = "" | |
242 | ||
243 | dialog = Gtk.MessageDialog(self, 0, | |
244 | Gtk.MessageType.ERROR, | |
245 | Gtk.ButtonsType.NONE, | |
246 | "Faraday has lost connection to CouchDB. " | |
247 | "The program has reverted back to the " | |
248 | "filesystem database. Fix the connection " | |
249 | "and re-enter the CouchDB URL in the " | |
250 | "preferences settings." + explanation) | |
251 | dialog.set_modal(True) | |
252 | ||
253 | retry_button = dialog.add_button("Retry connection?", 42) | |
254 | retry_button.connect("clicked", handle_connection_lost, dialog) | |
255 | ||
256 | cancel_button = dialog.add_button("Cancel", 0) | |
257 | cancel_button.connect("clicked", destroy_dialog) | |
258 | ||
259 | dialog.run() | |
260 | ||
210 | 261 | def do_new_log(self, text): |
211 | 262 | """To be used on a new_log signal. Calls a method on log to append |
212 | 263 | to it""" |
325 | 376 | return toolbar |
326 | 377 | |
327 | 378 | def new_tab(self, scrolled_window): |
328 | """The on_new_terminal_button redirects here. Tells the window | |
329 | to create pretty much a clone of itself when the user wants a new | |
330 | tab""" | |
379 | """The on_new_terminal_button redirects here from the application. | |
380 | The scrolled_window will be a scrolled window containing only a VTE | |
381 | terminal. | |
382 | """ | |
331 | 383 | |
332 | 384 | terminal = scrolled_window.get_children()[0] |
333 | 385 | terminal.connect("child_exited", self.on_terminal_exit) |
334 | 386 | self.tab_number += 1 |
335 | tab_number = self.tab_number | |
336 | 387 | pageN = self.terminalBox(scrolled_window) |
337 | self.notebook.append_page(pageN, Gtk.Label(str(tab_number+1))) | |
388 | self.notebook.append_page(pageN, Gtk.Label(str(self.tab_number+1))) | |
338 | 389 | self.notebook.show_all() |
339 | 390 | |
340 | 391 | def delete_tab(self, button=None): |
382 | 433 | |
383 | 434 | def on_terminal_exit(self, terminal, status): |
384 | 435 | """Really, it is *very* similar to delete_tab, but in this case |
385 | we want to make sure that we restart Faraday is the user | |
436 | we want to make sure that we restart Faraday if the user | |
386 | 437 | is not sure if he wants to exit""" |
387 | ||
388 | 438 | self.delete_tab() |
389 | 439 | terminal.startFaraday() |
0 | from gi.repository import Gtk | |
1 | from functools import wraps | |
2 | ||
3 | ||
4 | def scrollable(width=-1, height=-1): | |
5 | """A function that takes optinal width and height and returns | |
6 | the scrollable decorator. -1 is the default GTK option for both | |
7 | width and height.""" | |
8 | ||
9 | def scrollable_decorator(func): | |
10 | """Takes a function and returns the scroll_object_wrapper.""" | |
11 | ||
12 | @wraps(func) | |
13 | def scroll_object_wrapper(*args, **kwargs): | |
14 | """Takes arguments and obtains the original object from | |
15 | func(*args, **kwargs). Creates a box and puts the original | |
16 | inside that box. Creates a scrolled window and puts the | |
17 | box inside it. | |
18 | """ | |
19 | ||
20 | box = Gtk.Box() | |
21 | original = func(*args, **kwargs) | |
22 | scrolled_box = Gtk.ScrolledWindow(None, None) | |
23 | scrolled_box.set_min_content_width(width) | |
24 | scrolled_box.set_min_content_height(height) | |
25 | scrolled_box.add(original) | |
26 | box.pack_start(scrolled_box, True, True, 0) | |
27 | return box | |
28 | ||
29 | return scroll_object_wrapper | |
30 | ||
31 | return scrollable_decorator |
7 | 7 | ''' |
8 | 8 | import gi |
9 | 9 | import re |
10 | import webbrowser | |
10 | 11 | |
11 | 12 | gi.require_version('Gtk', '3.0') |
12 | 13 | |
13 | 14 | from gi.repository import Gtk, GdkPixbuf, Gdk |
14 | 15 | from persistence.persistence_managers import CouchDbManager |
15 | from utils.common import checkSSL | |
16 | 16 | from config.configuration import getInstanceConfiguration |
17 | 17 | from model import guiapi |
18 | from decorators import scrollable | |
18 | 19 | |
19 | 20 | |
20 | 21 | CONF = getInstanceConfiguration() |
22 | ||
21 | 23 | |
22 | 24 | class PreferenceWindowDialog(Gtk.Window): |
23 | 25 | """Sets up a preference dialog with basically nothing more than a |
25 | 27 | Takes a callback function to the mainapp so that it can refresh the |
26 | 28 | workspace list and information""" |
27 | 29 | |
28 | def __init__(self, callback, parent): | |
30 | def __init__(self, reload_ws_callback, connect_to_couch, parent): | |
29 | 31 | Gtk.Window.__init__(self, title="Preferences") |
30 | 32 | self.parent = parent |
31 | 33 | self.set_modal(True) |
32 | 34 | self.set_size_request(400, 100) |
33 | 35 | self.set_type_hint(Gdk.WindowTypeHint.DIALOG) |
34 | self.connect("key_press_event", on_scape_destroy) | |
36 | self.connect("key_press_event", key_reactions) | |
35 | 37 | self.set_transient_for(parent) |
36 | self.timeout_id = None | |
37 | self.reloadWorkspaces = callback | |
38 | ||
39 | vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) | |
40 | self.add(vbox) | |
41 | ||
42 | self.label = Gtk.Label() | |
43 | self.label.set_text("Your Couch IP") | |
44 | vbox.pack_start(self.label, True, False, 10) | |
38 | self.reloadWorkspaces = reload_ws_callback | |
39 | self.connectCouchCallback = connect_to_couch | |
40 | ||
41 | main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) | |
42 | ||
43 | ip_label = Gtk.Label() | |
44 | ip_label.set_text("Your Couch IP") | |
45 | main_box.pack_start(ip_label, True, False, 10) | |
45 | 46 | |
46 | 47 | couch_uri = CONF.getCouchURI() |
47 | self.entry = Gtk.Entry() | |
48 | self.ip_entry = Gtk.Entry() | |
48 | 49 | text = couch_uri if couch_uri else "http://127.0.0.1:5050" |
49 | self.entry.set_text(text) | |
50 | vbox.pack_start(self.entry, True, False, 10) | |
51 | ||
52 | hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) | |
53 | vbox.pack_end(hbox, False, True, 10) | |
54 | ||
55 | self.OK_button = Gtk.Button.new_with_label("OK") | |
56 | self.OK_button.connect("clicked", self.on_click_OK) | |
57 | ||
58 | hbox.pack_start(self.OK_button, False, True, 10) | |
59 | ||
60 | self.cancel_button = Gtk.Button.new_with_label("Cancel") | |
61 | self.cancel_button.connect("clicked", self.on_click_cancel) | |
62 | hbox.pack_end(self.cancel_button, False, True, 10) | |
63 | ||
64 | def on_click_OK(self, button): | |
65 | """Defines what happens when user clicks OK button""" | |
66 | repourl = self.entry.get_text() | |
67 | if not CouchDbManager.testCouch(repourl): | |
68 | errorDialog(self, "The provided URL is not valid", | |
69 | "Are you sure CouchDB is running?") | |
70 | elif repourl.startswith("https://"): | |
71 | if not checkSSL(repourl): | |
72 | errorDialog("The SSL certificate validation has failed") | |
73 | else: | |
74 | CONF.setCouchUri(repourl) | |
75 | CONF.saveConfig() | |
76 | self.reloadWorkspaces() | |
50 | self.ip_entry.set_text(text) | |
51 | main_box.pack_start(self.ip_entry, True, False, 10) | |
52 | ||
53 | button_box = Gtk.Box(spacing=6) | |
54 | main_box.pack_end(button_box, False, True, 10) | |
55 | ||
56 | OK_button = Gtk.Button.new_with_label("OK") | |
57 | OK_button.connect("clicked", self.on_click_ok) | |
58 | ||
59 | button_box.pack_start(OK_button, False, True, 10) | |
60 | ||
61 | cancel_button = Gtk.Button.new_with_label("Cancel") | |
62 | cancel_button.connect("clicked", self.on_click_cancel) | |
63 | button_box.pack_end(cancel_button, False, True, 10) | |
64 | ||
65 | self.add(main_box) | |
66 | ||
67 | def on_click_ok(self, button=None): | |
68 | """Button is useless, only there because GTK likes it. Takes the | |
69 | repourl (Couch IP) from self.ip_entry and connect to it if possible. | |
70 | """ | |
71 | repourl = self.ip_entry.get_text() | |
72 | if self.connectCouchCallback(repourl): # success! | |
77 | 73 | self.destroy() |
78 | 74 | |
79 | def on_click_cancel(self, button): | |
75 | def on_click_cancel(self, button=None): | |
80 | 76 | self.destroy() |
81 | 77 | |
82 | 78 | |
85 | 81 | a description and a type for a new workspace. Also checks that the |
86 | 82 | those attributes don't correspond to an existing workspace""" |
87 | 83 | |
88 | def __init__(self, callback, workspace_manager, sidebar, parent, | |
84 | def __init__(self, create_ws_callback, workspace_manager, sidebar, parent, | |
89 | 85 | title=None): |
90 | 86 | |
91 | 87 | Gtk.Window.__init__(self, title="Create New Workspace") |
92 | 88 | self.set_type_hint(Gdk.WindowTypeHint.DIALOG) |
93 | 89 | self.set_transient_for(parent) |
94 | 90 | self.set_modal(True) |
95 | self.connect("key_press_event", on_scape_destroy) | |
91 | self.connect("key_press_event", key_reactions) | |
96 | 92 | self.set_size_request(200, 200) |
97 | self.timeout_id = None | |
98 | self.callback = callback | |
93 | self.create_ws_callback = create_ws_callback | |
99 | 94 | self.sidebar = sidebar |
100 | ||
101 | self.mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) | |
102 | ||
103 | self.nameBox = Gtk.Box(spacing=6) | |
104 | self.name_label = Gtk.Label() | |
105 | self.name_label.set_text("Name: ") | |
95 | self.workspace_manager = workspace_manager | |
96 | self.title = title | |
97 | ||
98 | self.warning_label = self.create_warning_label() | |
99 | self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) | |
100 | ||
101 | name_box = self.create_name_box() | |
102 | description_box = self.create_description_box() | |
103 | type_box = self.create_type_box() | |
104 | button_box = self.create_button_box() | |
105 | ||
106 | self.main_box.pack_start(name_box, False, False, 10) | |
107 | self.main_box.pack_start(description_box, False, False, 10) | |
108 | self.main_box.pack_start(type_box, False, False, 10) | |
109 | self.main_box.pack_start(self.warning_label, False, False, 10) | |
110 | self.main_box.pack_end(button_box, False, False, 10) | |
111 | ||
112 | self.main_box.show() | |
113 | self.add(self.main_box) | |
114 | ||
115 | def create_name_box(self): | |
116 | """Return a box with a Name label left of an entry.""" | |
117 | name_box = Gtk.Box(spacing=6) | |
118 | name_label = Gtk.Label() | |
119 | name_label.set_text("Name: ") | |
106 | 120 | self.name_entry = Gtk.Entry() |
107 | if title is not None: | |
121 | if self.title is not None: | |
108 | 122 | self.name_entry.set_text(title) |
109 | self.nameBox.pack_start(self.name_label, False, False, 10) | |
110 | self.nameBox.pack_end(self.name_entry, True, True, 10) | |
111 | self.nameBox.show() | |
112 | ||
113 | self.descrBox = Gtk.Box(spacing=6) | |
114 | self.descr_label = Gtk.Label() | |
115 | self.descr_label.set_text("Description: ") | |
116 | self.descr_entry = Gtk.Entry() | |
117 | self.descrBox.pack_start(self.descr_label, False, False, 10) | |
118 | self.descrBox.pack_end(self.descr_entry, True, True, 10) | |
119 | self.descrBox.show() | |
120 | ||
121 | self.typeBox = Gtk.Box(spacing=6) | |
122 | self.type_label = Gtk.Label() | |
123 | self.type_label.set_text("Type: ") | |
124 | self.comboBox = Gtk.ComboBoxText() | |
125 | for w in workspace_manager.getAvailableWorkspaceTypes(): | |
126 | self.comboBox.append_text(w) | |
127 | self.typeBox.pack_start(self.type_label, False, False, 10) | |
128 | self.typeBox.pack_end(self.comboBox, True, True, 10) | |
129 | self.typeBox.show() | |
130 | ||
131 | self.buttonBox = Gtk.Box(spacing=6) | |
132 | self.OK_button = Gtk.Button.new_with_label("OK") | |
133 | self.OK_button.connect("clicked", self.on_click_OK) | |
134 | self.cancel_button = Gtk.Button.new_with_label("Cancel") | |
135 | self.cancel_button.connect("clicked", self.on_click_cancel) | |
136 | self.buttonBox.pack_start(self.OK_button, False, False, 10) | |
137 | self.buttonBox.pack_end(self.cancel_button, False, False, 10) | |
138 | self.buttonBox.show() | |
139 | ||
140 | self.mainBox.pack_start(self.nameBox, False, False, 10) | |
141 | self.mainBox.pack_start(self.descrBox, False, False, 10) | |
142 | self.mainBox.pack_start(self.typeBox, False, False, 10) | |
143 | self.mainBox.pack_end(self.buttonBox, False, False, 10) | |
144 | ||
145 | self.mainBox.show() | |
146 | self.add(self.mainBox) | |
147 | ||
148 | def on_click_OK(self, button): | |
123 | name_box.pack_start(name_label, False, False, 10) | |
124 | name_box.pack_end(self.name_entry, True, True, 10) | |
125 | return name_box | |
126 | ||
127 | def create_description_box(self): | |
128 | """Return a box with a Description label left of an entry.""" | |
129 | description_box = Gtk.Box(spacing=6) | |
130 | description_label = Gtk.Label() | |
131 | description_label.set_text("Description: ") | |
132 | self.description_entry = Gtk.Entry() | |
133 | description_box.pack_start(description_label, False, False, 10) | |
134 | description_box.pack_end(self.description_entry, True, True, 10) | |
135 | return description_box | |
136 | ||
137 | def create_type_box(self): | |
138 | """Return a box with a Type label left of a combo box""" | |
139 | type_box = Gtk.Box(spacing=6) | |
140 | type_label = Gtk.Label() | |
141 | type_label.set_text("Type: ") | |
142 | self.type_comboBox = Gtk.ComboBoxText() | |
143 | self.type_comboBox.connect("changed", self.on_select_ws_type) | |
144 | for w in self.workspace_manager.getAvailableWorkspaceTypes(): | |
145 | self.type_comboBox.append_text(w) | |
146 | self.type_comboBox.set_active(0) | |
147 | type_box.pack_start(type_label, False, False, 10) | |
148 | type_box.pack_end(self.type_comboBox, True, True, 10) | |
149 | return type_box | |
150 | ||
151 | def create_warning_label(self): | |
152 | """Return a label with a warning if the user has FS selected as the | |
153 | desired WS type. | |
154 | """ | |
155 | warning_label = Gtk.Label() | |
156 | warning_label.set_no_show_all(True) | |
157 | warning_label.set_markup("<b>WARNING: </b> The FS (Filesystem) " | |
158 | "databases are deprecated and strongly " | |
159 | "discouraged. \n You will <b>not</b> be able " | |
160 | "to edit the information provided by Faraday " | |
161 | "with a FileSystem DB. \n Please " | |
162 | "set up CouchDB and use it as the database " | |
163 | "for your workspaces.") | |
164 | return warning_label | |
165 | ||
166 | def create_button_box(self): | |
167 | """Return a box with OK and cancel buttons.""" | |
168 | button_box = Gtk.Box(spacing=6) | |
169 | OK_button = Gtk.Button.new_with_label("OK") | |
170 | OK_button.connect("clicked", self.on_click_ok) | |
171 | cancel_button = Gtk.Button.new_with_label("Cancel") | |
172 | cancel_button.connect("clicked", self.on_click_cancel) | |
173 | button_box.pack_start(OK_button, False, False, 10) | |
174 | button_box.pack_end(cancel_button, False, False, 10) | |
175 | return button_box | |
176 | ||
177 | def on_click_ok(self, button=None): | |
178 | """Check if the name provided for the WS is valid. If so, | |
179 | create it and add it to the sidebar. If not, show error. | |
180 | """ | |
149 | 181 | letters_or_numbers = r"^[a-z][a-z0-9\_\$()\+\-\/]*$" |
150 | 182 | res = re.match(letters_or_numbers, str(self.name_entry.get_text())) |
151 | 183 | if res: |
152 | if self.callback is not None: | |
153 | self.__name_txt = str(self.name_entry.get_text()) | |
154 | self.__desc_txt = str(self.descr_entry.get_text()) | |
155 | self.__type_txt = str(self.comboBox.get_active_text()) | |
156 | creation_ok = self.callback(self.__name_txt, | |
157 | self.__desc_txt, | |
158 | self.__type_txt) | |
159 | if creation_ok: | |
160 | self.sidebar.addWorkspace(self.__name_txt) | |
161 | self.destroy() | |
184 | ws_name = str(self.name_entry.get_text()) | |
185 | ws_desc = str(self.description_entry.get_text()) | |
186 | ws_type = str(self.type_comboBox.get_active_text()) | |
187 | creation_ok = self.create_ws_callback(ws_name, | |
188 | ws_desc, | |
189 | ws_type) | |
190 | if creation_ok: | |
191 | self.sidebar.addWorkspace(ws_name) | |
192 | else: | |
193 | errorDialog(self, "Something went wrong when creating " | |
194 | "the new workspace.") | |
195 | self.destroy() | |
162 | 196 | else: |
163 | 197 | errorDialog(self, "Invalid workspace name", |
164 | 198 | "A workspace must be named with " |
167 | 201 | "characters. The name has to start" |
168 | 202 | " with a lowercase letter") |
169 | 203 | |
204 | def on_select_ws_type(self, combo_box): | |
205 | if combo_box.get_active_text() == 'FS': | |
206 | self.warning_label.show() | |
207 | else: | |
208 | self.warning_label.hide() | |
209 | ||
170 | 210 | def on_click_cancel(self, button): |
171 | 211 | self.destroy() |
172 | 212 | |
184 | 224 | self.set_type_hint(Gdk.WindowTypeHint.DIALOG) |
185 | 225 | self.set_transient_for(parent) |
186 | 226 | self.set_modal(True) |
187 | self.connect("key_press_event", on_scape_destroy) | |
227 | self.connect("key_press_event", key_reactions) | |
188 | 228 | self.set_size_request(800, 300) |
229 | self.plugin_manager = plugin_manager | |
189 | 230 | |
190 | 231 | if plugin_manager is not None: |
191 | 232 | self.plugin_settings = plugin_manager.getSettings() |
198 | 239 | self.setSettingsView() |
199 | 240 | |
200 | 241 | plugin_info = self.createPluginInfo(plugin_manager) |
201 | pluginList = self.createPluginListView(plugin_info) | |
202 | scroll_pluginList = Gtk.ScrolledWindow(None, None) | |
203 | scroll_pluginList.add(pluginList) | |
204 | scroll_pluginList.set_min_content_width(300) | |
205 | pluginListBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
206 | pluginListBox.pack_start(scroll_pluginList, True, True, 0) | |
242 | plugin_list = self.createPluginListView(plugin_info) | |
243 | left_side_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
244 | left_side_box.pack_start(plugin_list, True, True, 0) | |
207 | 245 | |
208 | 246 | buttonBox = Gtk.Box() |
209 | 247 | OK_button = Gtk.Button.new_with_label("OK") |
210 | 248 | cancel_button = Gtk.Button.new_with_label("Cancel") |
211 | OK_button.connect("clicked", self.on_click_OK, plugin_manager) | |
249 | OK_button.connect("clicked", self.on_click_ok) | |
212 | 250 | cancel_button.connect("clicked", self.on_click_cancel) |
213 | 251 | buttonBox.pack_start(OK_button, True, True, 10) |
214 | 252 | buttonBox.pack_start(cancel_button, True, True, 10) |
215 | pluginListBox.pack_start(buttonBox, False, False, 10) | |
253 | ||
254 | left_side_box.pack_start(buttonBox, False, False, 10) | |
216 | 255 | |
217 | 256 | infoBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) |
218 | 257 | nameBox, versionBox, pluginVersionBox = [Gtk.Box() for i in range(3)] |
243 | 282 | self.pluginSpecsBox.pack_start(self.settings_view, True, True, 0) |
244 | 283 | |
245 | 284 | self.mainBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) |
246 | self.mainBox.pack_start(pluginListBox, False, True, 10) | |
285 | self.mainBox.pack_start(left_side_box, False, True, 10) | |
247 | 286 | self.mainBox.pack_end(self.pluginSpecsBox, True, True, 10) |
248 | 287 | |
249 | 288 | self.add(self.mainBox) |
250 | 289 | |
251 | def on_click_OK(self, button, plugin_manager): | |
290 | def on_click_ok(self, button=None): | |
252 | 291 | """On click OK button update the plugins settings and then destroy""" |
253 | if plugin_manager is not None: | |
254 | plugin_manager.updateSettings(self.plugin_settings) | |
292 | if self.plugin_manager is not None: | |
293 | self.plugin_manager.updateSettings(self.plugin_settings) | |
255 | 294 | self.destroy() |
256 | 295 | |
257 | 296 | def on_click_cancel(self, button): |
275 | 314 | sorted_plugin_info.set_sort_column_id(1, Gtk.SortType.ASCENDING) |
276 | 315 | return sorted_plugin_info |
277 | 316 | |
317 | @scrollable(width=300) | |
278 | 318 | def createPluginListView(self, plugin_info): |
279 | 319 | """Creates the view for the left-hand side list of the dialog. |
280 | 320 | It uses an instance of the plugin manager to get a list |
383 | 423 | |
384 | 424 | class HostInfoDialog(Gtk.Window): |
385 | 425 | """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 | |
426 | basic information in labels as well as interfaces/services in a treeview. | |
427 | ||
428 | While working in this class, keep in mind the distinction between | |
429 | selections (which are part of a model that holds data about an object as | |
430 | strings and ints) and the object per se, which are in the model folder and | |
431 | are totally alien to GTK. | |
387 | 432 | """ |
388 | def __init__(self, parent, host): | |
433 | def __init__(self, parent, active_ws_name, is_ws_couch, host): | |
389 | 434 | """Creates a window with the information about a given hosts. |
390 | 435 | The parent is needed so the window can set transient for |
391 | 436 | """ |
392 | Gtk.Window.__init__(self, | |
393 | title="Host " + host.name + " information") | |
437 | ||
438 | window_title = "Host " + host.name + " information" | |
439 | Gtk.Window.__init__(self, title=window_title) | |
440 | ||
394 | 441 | self.set_transient_for(parent) |
395 | 442 | self.set_size_request(1200, 500) |
396 | 443 | self.set_modal(True) |
397 | self.connect("key_press_event", on_scape_destroy) | |
444 | self.connect("key_press_event", key_reactions) | |
445 | ||
446 | self.is_ws_couch = is_ws_couch | |
447 | ||
398 | 448 | self.host = host |
449 | self.model = self.create_model(self.host) | |
450 | host_info = self.model[0] | |
451 | ||
452 | host_id = self.model[0][0] | |
453 | couch_url = CONF.getCouchURI() | |
454 | base_url = couch_url + "/reports/_design/reports/index.html#/host/ws/" | |
455 | self.edit_url = base_url + active_ws_name + "/hid/" + host_id | |
456 | ||
457 | host_info_frame = self.create_host_info_frame(host_info) | |
399 | 458 | |
400 | 459 | self.specific_info = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) |
401 | 460 | self.specific_info_frame = self.create_scroll_frame( |
402 | 461 | self.specific_info, |
403 | 462 | "Service Information") |
404 | 463 | |
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, | |
464 | self.vuln_info = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) | |
465 | self.vuln_info_frame = self.create_scroll_frame( | |
466 | self.vuln_info, | |
408 | 467 | "Vulnerability Information") |
409 | 468 | |
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) | |
469 | main_tree = self.create_main_tree_view(self.model) | |
470 | vuln_list = self.create_vuln_list() | |
471 | ||
472 | button_box = self.create_button_box() | |
414 | 473 | |
415 | 474 | main_box = Gtk.Box() |
416 | 475 | |
417 | 476 | info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) |
418 | info_box.pack_start(basic_info_frame, True, True, 10) | |
477 | info_box.pack_start(host_info_frame, True, True, 10) | |
419 | 478 | 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) | |
479 | info_box.pack_start(self.vuln_info_frame, True, True, 10) | |
480 | info_box.pack_start(button_box, False, False, 10) | |
422 | 481 | |
423 | 482 | main_tree_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) |
424 | main_tree_box.pack_start(children_of_host_tree, True, True, 10) | |
483 | main_tree_box.pack_start(main_tree, True, True, 10) | |
425 | 484 | main_tree_box.pack_start(Gtk.Box(), False, False, 10) |
426 | 485 | |
427 | 486 | vuln_list_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) |
428 | vuln_list_box.pack_start(self.create_vuln_tree_box(), True, True, 10) | |
487 | vuln_list_box.pack_start(vuln_list, True, True, 10) | |
429 | 488 | vuln_list_box.pack_start(Gtk.Box(), False, False, 10) |
430 | 489 | |
431 | 490 | main_box.pack_start(main_tree_box, False, False, 5) |
434 | 493 | |
435 | 494 | self.add(main_box) |
436 | 495 | |
496 | def create_button_box(self): | |
497 | """Creates an horizontal box to hold the buttons.""" | |
498 | button_box = Gtk.Box() | |
499 | ||
500 | ok_button = Gtk.Button.new_with_label("OK") | |
501 | ok_button.connect("clicked", self.on_click_ok) | |
502 | ||
503 | html_edit_url = '<a href="' + self.edit_url + '"> Edit host </a>' | |
504 | edit_button = Gtk.Button() | |
505 | edit_label = Gtk.Label() | |
506 | edit_label.set_markup(html_edit_url) | |
507 | edit_button.add(edit_label) | |
508 | edit_button.connect("clicked", self.on_edit_host) | |
509 | if not self.is_ws_couch: | |
510 | edit_button.set_sensitive(False) | |
511 | edit_button.set_tooltip_text("You need to be on a CouchDB " | |
512 | "workspace to edit information") | |
513 | ||
514 | button_box.pack_start(edit_button, True, True, 0) | |
515 | button_box.pack_start(ok_button, True, True, 0) | |
516 | return button_box | |
517 | ||
518 | def on_edit_host(self, button): | |
519 | """Tries to open self.edit_url (url which directs to the host in the | |
520 | web ui) in the default browser.""" | |
521 | webbrowser.open(self.edit_url, new = 2) | |
522 | ||
523 | ||
437 | 524 | 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 | """ | |
525 | """Create a scrollable frame containing inner_box and with label_str | |
526 | as its title. | |
527 | """ | |
528 | ||
446 | 529 | label = Gtk.Label() |
447 | 530 | label.set_markup("<big>" + label_str + "</big>") |
448 | 531 | |
459 | 542 | |
460 | 543 | return frame |
461 | 544 | |
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 | |
545 | def create_host_info_frame(self, host_info): | |
546 | """Return a box where the basic information about the host | |
547 | lives in labels. It include names, OS, Owned status and vulnerability | |
465 | 548 | count. |
466 | 549 | """ |
467 | 550 | box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) |
468 | 551 | |
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() | |
552 | prop_names = self.get_properties_names("Host") | |
553 | self.show_info_in_box(host_info, prop_names, box) | |
554 | ||
555 | host_info_frame = self.create_scroll_frame(box, "Host Information") | |
556 | ||
557 | return host_info_frame | |
558 | ||
559 | @scrollable(width=250) | |
560 | def create_vuln_list(self): | |
561 | """Creates a simple view of vulnerabilities for the object | |
562 | and returns a box containing it. The vuln_list will be a value of the | |
563 | instance. | |
564 | """ | |
565 | ||
509 | 566 | self.vuln_list = Gtk.TreeView() |
510 | 567 | self.vuln_list.set_activate_on_single_click(True) |
511 | 568 | renderer = Gtk.CellRendererText() |
512 | 569 | column = Gtk.TreeViewColumn("Vulnerabilities", renderer, text=1) |
570 | column.set_sort_column_id(1) | |
513 | 571 | self.vuln_list.append_column(column) |
514 | 572 | |
515 | 573 | vuln_selection = self.vuln_list.get_selection() |
516 | 574 | vuln_selection.connect("changed", self.on_vuln_selection) |
517 | 575 | |
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 | |
576 | return self.vuln_list | |
577 | ||
578 | def create_model(self, host): | |
579 | """Return a model for the given host. It holds quite a bit of info. | |
580 | It has 15 columns holding the host ID and name as parent, | |
581 | all the information about the interfaces of that host and all the | |
582 | information about the services of those interfaces. | |
583 | ||
584 | The model is difficult to draw because of its nested nature, but | |
585 | you can think of it like this, keeping in mind each node has | |
586 | several columns | |
587 | ||
588 | HOST | |
589 | -----> INTERFACE1 | |
590 | ------------> SERVICE1 | |
591 | ------------> SERVICE2 | |
592 | -----> INTERFACE2 | |
593 | -----------> SERVICE1 | |
594 | -----------> SERVICE2 | |
595 | ||
596 | And so on and so on, like Zizek says. | |
597 | """ | |
598 | ||
599 | # those are 13 strings | |
537 | 600 | model = Gtk.TreeStore(str, str, str, str, str, str, str, |
538 | 601 | str, str, str, str, str, str) |
539 | 602 | |
542 | 605 | # the other columns with dummy info |
543 | 606 | |
544 | 607 | display_str = host.getName() + " (" + str(len(host.getVulns())) + ")" |
545 | host_iter = model.append(None, [host.getID(), host.getName(), | |
546 | "", "", "", "", "", "", | |
547 | "", "", "", "", display_str]) | |
608 | owned_status = ("Yes" if host.isOwned() else "No") | |
609 | host_position = model.append(None, [host.getID(), host.getName(), | |
610 | host.getOS(), owned_status, | |
611 | str(len(host.getVulns())), "", | |
612 | "", "", "", "", "", "", | |
613 | display_str]) | |
614 | ||
615 | # some convenient functions just to separate and clarify what | |
616 | # the code does. they are used in the nested for loop directly | |
617 | # below them. | |
548 | 618 | |
549 | 619 | def lst_to_str(lst): |
550 | 620 | """Convenient function to avoid this long line everywhere""" |
551 | 621 | return ', '.join([str(word) for word in lst if word]) |
552 | 622 | |
553 | for interface in interfaces: | |
623 | def add_interface_to_host_in_model(interface, host_pos, model): | |
624 | """Append an interface to the host within a model. | |
625 | Return the tree_iter represeting the position of the interface | |
626 | within the model. Modifies the model. | |
627 | """ | |
554 | 628 | ipv4_dic = interface.getIPv4() |
555 | 629 | ipv6_dic = interface.getIPv6() |
556 | 630 | vulns = interface.getVulns() |
557 | 631 | display_str = interface.getName() + " (" + str(len(vulns)) + ")" |
558 | 632 | |
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(), | |
633 | position = model.append(host_pos, [interface.getID(), | |
634 | interface.getName(), | |
635 | interface.getDescription(), | |
636 | interface.getMAC(), | |
637 | ipv4_dic['mask'], | |
638 | ipv4_dic['gateway'], | |
639 | lst_to_str(ipv4_dic['DNS']), | |
640 | ipv4_dic['address'], | |
641 | ipv6_dic['prefix'], | |
642 | ipv6_dic['gateway'], | |
643 | lst_to_str(ipv6_dic['DNS']), | |
644 | ipv6_dic['address'], | |
645 | display_str]) | |
646 | return position | |
647 | ||
648 | def add_service_to_interface_in_model(service, interface_pos, model): | |
649 | """Append a service to an interface at interface_pos in the given | |
650 | model. Return None. Modifies the model""" | |
651 | vulns = service.getVulns() | |
652 | display_str = service.getName() + " (" + str(len(vulns)) + ")" | |
653 | model.append(interface_pos, [service.getID(), | |
581 | 654 | service.getName(), |
582 | 655 | service.getDescription(), |
583 | 656 | service.getProtocol(), |
587 | 660 | "Yes" if service.isOwned() else "No", |
588 | 661 | "", "", "", "", display_str]) |
589 | 662 | |
590 | self.view = Gtk.TreeView(model) | |
591 | self.view.set_activate_on_single_click(True) | |
663 | interfaces = host.getAllInterfaces() | |
664 | for interface in interfaces: | |
665 | interface_position = add_interface_to_host_in_model(interface, | |
666 | host_position, | |
667 | model) | |
668 | services = interface.getAllServices() | |
669 | for service in services: | |
670 | add_service_to_interface_in_model(service, interface_position, | |
671 | model) | |
672 | ||
673 | return model | |
674 | ||
675 | @scrollable(width=250) | |
676 | def create_main_tree_view(self, model): | |
677 | """Return a box containing the main tree (the one showing | |
678 | Host/Interfaces/Services) as its content. | |
679 | """ | |
680 | view = Gtk.TreeView(model) | |
681 | view.set_activate_on_single_click(True) | |
682 | view.set_enable_tree_lines(True) | |
683 | view.expand_all() | |
592 | 684 | |
593 | 685 | renderer = Gtk.CellRendererText() |
594 | 686 | column = Gtk.TreeViewColumn("Host/Interfaces/Services", |
595 | 687 | renderer, text=12) |
596 | 688 | |
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 | """ | |
689 | view.append_column(column) | |
690 | view.set_expander_column(column) | |
691 | selection = view.get_selection() | |
692 | selection.connect("changed", self.on_main_tree_selection) | |
693 | ||
694 | return view | |
695 | ||
696 | def on_main_tree_selection(self, tree_selection): | |
697 | """Fire up neccesary actions when selection on the main tree changes""" | |
614 | 698 | model, tree_iter = tree_selection.get_selected() |
699 | object_info = model[tree_iter] | |
700 | ||
615 | 701 | 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: | |
702 | object_type = {0: 'Host', 1: 'Interface', 2: 'Service'}[iter_depth] | |
703 | ||
704 | if object_type == 'Host': | |
619 | 705 | 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)) | |
706 | ||
707 | elif object_type == 'Interface' or object_type == 'Service': | |
708 | self.clear(self.specific_info) | |
709 | self.change_label_in_frame(self.specific_info_frame, object_type) | |
710 | prop_names = self.get_properties_names(object_type) | |
711 | self.show_info_in_box(object_info, prop_names, self.specific_info) | |
712 | actual_object = self.get_object(object_info, object_type) | |
713 | vuln_model = self.create_vuln_model(actual_object) | |
714 | self.set_vuln_model(vuln_model) | |
635 | 715 | |
636 | 716 | 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""" | |
717 | """Fill the vuln_info box with the vulnerability selected. | |
718 | ||
719 | The try/except block is necesary 'cause GTK | |
720 | is silly (ie: doesn't behave like it would be best for me now) | |
721 | and will emit the selection changed signal if the model | |
722 | changes even if nothing is selected. | |
723 | """ | |
641 | 724 | |
642 | 725 | model, vuln_iter = vuln_selection.get_selected() |
643 | self.specific_vuln_info.foreach(self.reset_vuln_info) | |
644 | 726 | try: |
645 | 727 | selected = model[vuln_iter] |
646 | self.show_vuln_info(selected) | |
728 | vuln_type = selected[0] | |
729 | self.clear(self.vuln_info) | |
730 | is_vuln_web = vuln_type == "VulnerabilityWeb" | |
731 | frame_title = "Vulnerability Web" if is_vuln_web else "Vulnerability" | |
732 | self.change_label_in_frame(self.vuln_info_frame, | |
733 | frame_title) | |
734 | prop_names = self.get_properties_names(vuln_type) | |
735 | self.show_info_in_box(selected, prop_names, | |
736 | self.vuln_info) | |
647 | 737 | except TypeError: |
648 | 738 | return False |
649 | 739 | |
650 | 740 | def set_vuln_model(self, model): |
741 | """Sets the vulnerability view to show the given model""" | |
651 | 742 | self.vuln_list.set_model(model) |
652 | 743 | |
653 | 744 | def create_vuln_model(self, obj): |
654 | """Creates a model for the vulnerabilities of the selected object""" | |
745 | """Return the model for the vulnerabilities of the obj object. | |
746 | It will be sorted alphabetically. | |
747 | """ | |
655 | 748 | # those are 15 strings |
656 | 749 | model = Gtk.ListStore(str, str, str, str, str, str, str, str, |
657 | 750 | str, str, str, str, str, str, str) |
660 | 753 | for vuln in vulns: |
661 | 754 | _type = vuln.class_signature |
662 | 755 | if _type == "Vulnerability": |
756 | # again filling up the model with dumb strings | |
757 | # because gtk | |
663 | 758 | model.append([_type, vuln.getName(), vuln.getDescription(), |
664 | 759 | vuln.getData(), vuln.getSeverity(), |
665 | 760 | ', '.join(vuln.getRefs()), |
673 | 768 | vuln.getResponse(), vuln.getMethod(), |
674 | 769 | vuln.getPname(), vuln.getParams(), |
675 | 770 | 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) | |
771 | #sort it! | |
772 | sorted_model = Gtk.TreeModelSort(model=model) | |
773 | sorted_model.set_sort_column_id(1, Gtk.SortType.ASCENDING) | |
774 | ||
775 | return sorted_model | |
776 | ||
777 | def change_label_in_frame(self, frame, string): | |
778 | """Changes the label in the given frame to 'string Information'""" | |
779 | label = frame.get_label_widget() | |
780 | label.set_markup("<big>" + string + " " + "Information" + "</big>") | |
781 | ||
782 | def show_info_in_box(self, object_info, property_names, box): | |
783 | """Appends several boxes vertically to the box. The appended boxes will | |
784 | all contain two labels, together forming something like this: | |
785 | '<b>property_name:</b> object_info'. It will also append a separator | |
786 | on top of each one of these boxes. | |
787 | ||
788 | It is important to notice that the first element of object_info | |
789 | is ignored. This is because of how the models in this class contain | |
790 | information. Thus, there'll be as many of this small boxes as | |
791 | len(property_names) minus one, read next paragraph. | |
792 | """ | |
793 | ||
794 | for index, prop_name in enumerate(property_names, start=1): | |
795 | if index != 1: | |
796 | # do not append to the first prop_name | |
797 | separator = Gtk.Separator.new(orientation=Gtk.Orientation.HORIZONTAL) | |
798 | box.pack_start(separator, False, True, 0) | |
799 | ||
800 | prop_box = Gtk.Box() | |
801 | prop_value = object_info[index] | |
802 | ||
803 | prop_label = Gtk.Label() | |
804 | prop_label.set_markup("<b> %s </b>" % (prop_name)) | |
805 | prop_label.set_selectable(True) | |
806 | ||
807 | value_label = Gtk.Label(prop_value) | |
808 | value_label.set_selectable(True) | |
809 | prop_box.pack_start(prop_label, False, False, 0) | |
810 | prop_box.pack_start(value_label, False, False, 0) | |
811 | box.pack_start(prop_box, True, True, 0) | |
812 | ||
732 | 813 | box.show_all() |
733 | 814 | |
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): | |
815 | def get_object(self, selected_object, object_type): | |
816 | """Take a selection as selected_object and an object_type | |
817 | and return the actual object, not the model's selection. | |
818 | """ | |
819 | object_id = selected_object[0] | |
820 | if object_type == 'Interface': | |
821 | # an interface is a direct child of a host | |
822 | object_ = self.host.getInterface(object_id) | |
823 | elif object_type == 'Service': | |
824 | # a service is a grand-child of a host, so we should look | |
825 | # for its parent interface and ask her about the child | |
826 | parent_interface_iter = selected_object.get_parent() | |
827 | parent_interface_id = parent_interface_iter[0] | |
828 | parent_interface = self.host.getInterface(parent_interface_id) | |
829 | object_ = parent_interface.getService(object_id) | |
830 | ||
831 | return object_ | |
832 | ||
833 | def get_properties_names(self, object_type): | |
834 | """Return a list with the property names for objects of type | |
835 | Interface, Service, Vulnerability and VulnerabilityWeb (passed as a | |
836 | string). | |
837 | """ | |
838 | if object_type == "Host": | |
839 | property_names = ["Name: ", "OS: ", "Owned: ", | |
840 | "Vulnerabilities: "] | |
841 | ||
842 | if object_type == "Interface": | |
843 | property_names = ["Name: ", "Description: ", "MAC: ", | |
844 | "IPv4 Mask: ", "IPv4 Gateway: ", "IPv4 DNS: ", | |
845 | "IPv4 Address: ", "IPv6 Prefix: ", | |
846 | "IPv6 Gateway", "IPv6 DNS: ", | |
847 | "IPv6 Address: "] | |
848 | ||
849 | elif object_type == "Service": | |
850 | property_names = ["Name: ", "Description: ", "Protocol: ", | |
851 | "Status: ", "Port: ", "Version: ", "Is Owned?: "] | |
852 | ||
853 | elif object_type == "Vulnerability": | |
854 | property_names = ["Name: ", "Description: ", "Data: ", | |
855 | "Severity: ", "Refs: "] | |
856 | ||
857 | elif object_type == "VulnerabilityWeb": | |
858 | property_names = ["Name: ", "Description: ", "Data: ", | |
859 | "Severity: ", "Refs: ", "Path: ", | |
860 | "Website: ", "Request: ", "Response: ", | |
861 | "Method: ", "Pname: ", "Params: ", | |
862 | "Query: ", "Category: "] | |
863 | return property_names | |
864 | ||
865 | def clear(self, box): | |
866 | """Remove all the widgets from box.""" | |
867 | ||
868 | def remove(widget, box): | |
869 | """Removes widget from box""" | |
870 | box.remove(widget) | |
871 | ||
872 | box.foreach(remove, box) | |
873 | ||
874 | def on_click_ok(self, button=None): | |
745 | 875 | self.destroy() |
746 | 876 | |
747 | 877 | |
760 | 890 | self.set_transient_for(parent) |
761 | 891 | self.set_size_request(600, 400) |
762 | 892 | self.set_modal(True) |
763 | self.connect("key_press_event", on_scape_destroy) | |
893 | self.connect("key_press_event", key_reactions) | |
764 | 894 | self.conflicts = conflicts |
765 | 895 | self.conflict_n = 0 |
766 | 896 | self.current_conflict = self.conflicts[self.conflict_n] |
893 | 1023 | n. If first conflict, self.view will be none. If user is past the first |
894 | 1024 | conflict, self.view will not be none""" |
895 | 1025 | |
1026 | @scrollable() | |
1027 | def make_scrollable(view): | |
1028 | return view | |
1029 | ||
896 | 1030 | if self.view is None: |
897 | 1031 | |
898 | 1032 | renderer = Gtk.CellRendererText() |
924 | 1058 | self.second_view.append_column(prop2_column) |
925 | 1059 | self.second_view.append_column(obj2_column) |
926 | 1060 | |
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) | |
1061 | scrolled_view = make_scrollable(self.view) | |
1062 | second_scrolled_view = make_scrollable(self.second_view) | |
931 | 1063 | |
932 | 1064 | self.views_box.pack_start(scrolled_view, True, True, 5) |
933 | 1065 | self.views_box.pack_start(second_scrolled_view, True, True, 5) |
1093 | 1225 | """Preconditions: the model has 5 string columns, |
1094 | 1226 | len(attr[0]) == len(attr[1]) == len(props), |
1095 | 1227 | type(attr[0][i]) == type(attr[1][i]) for every i |
1096 | attr is a list of two tuples. the first tuple holds info about obj1, | |
1097 | the second about obj2. | |
1098 | props is the list with names of such attributes | |
1228 | attr is a list with two tuples. the first tuple holds info about obj1, | |
1229 | the second about obj2, for example: | |
1230 | [(name_obj1, ports_obj1), (name_obj2, porst_obj2)] | |
1231 | props is the list with names of such attributes, for example: | |
1232 | ["Name: ", "Ports: "] | |
1099 | 1233 | |
1100 | 1234 | Will return a model filled up with information as detailed in |
1101 | 1235 | self.create_conflicts_models. |
1131 | 1265 | # just use that color, screw highlights |
1132 | 1266 | return '#%02x%02x%02x' % (color1, color2, color3) |
1133 | 1267 | |
1134 | i = 0 | |
1135 | for prop in props: | |
1136 | first_raw_prop = attr[0][i] | |
1137 | sec_raw_prop = attr[1][i] | |
1268 | for index, prop in enumerate(props): | |
1269 | # remember props is a list like [(obj1_prop1, obj1_prop2...), | |
1270 | # (obj2_prop1, obj2, prop2...)] | |
1271 | first_raw_prop = attr[0][index] | |
1272 | sec_raw_prop = attr[1][index] | |
1138 | 1273 | first_prop = self.cook(first_raw_prop) |
1139 | 1274 | sec_prop = self.cook(sec_raw_prop) |
1140 | 1275 | |
1141 | 1276 | model.append([prop, first_prop, sec_prop, |
1142 | 1277 | decide_bg(), |
1143 | 1278 | decide_type(first_raw_prop)]) |
1144 | i += 1 | |
1145 | 1279 | |
1146 | 1280 | return model |
1147 | 1281 | |
1209 | 1343 | self.set_transient_for(parent) |
1210 | 1344 | self.set_size_request(400, 200) |
1211 | 1345 | self.set_modal(True) |
1212 | self.connect("key_press_event", on_scape_destroy) | |
1213 | ||
1214 | self.view = view | |
1346 | self.connect("key_press_event", key_reactions) | |
1215 | 1347 | self.destroy_notifications = callback |
1348 | ||
1349 | scrolled_list = self.create_view_box(view) | |
1216 | 1350 | |
1217 | 1351 | self.button = Gtk.Button() |
1218 | 1352 | self.button.set_label("OK") |
1219 | self.button.connect("clicked", self.on_click_OK) | |
1220 | ||
1221 | scrolled_list = Gtk.ScrolledWindow.new(None, None) | |
1222 | scrolled_list.set_min_content_width(200) | |
1223 | scrolled_list.set_min_content_height(350) | |
1224 | scrolled_list.add(self.view) | |
1353 | self.button.connect("clicked", self.on_click_ok) | |
1225 | 1354 | |
1226 | 1355 | self.mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) |
1227 | 1356 | self.mainBox.pack_start(scrolled_list, True, True, 0) |
1229 | 1358 | |
1230 | 1359 | self.add(self.mainBox) |
1231 | 1360 | |
1232 | def on_click_OK(self, button): | |
1361 | @scrollable(width=250, height=350) | |
1362 | def create_view_box(self, view): | |
1363 | return view | |
1364 | ||
1365 | def on_click_ok(self, button=None): | |
1233 | 1366 | self.destroy_notifications() |
1234 | 1367 | self.destroy() |
1235 | 1368 | |
1300 | 1433 | textBuffer = Gtk.TextBuffer() |
1301 | 1434 | textBuffer.set_text(error) |
1302 | 1435 | |
1436 | text_view_box = self.create_text_view_box(textBuffer) | |
1437 | ||
1438 | content = self.get_content_area() | |
1439 | ||
1440 | content.pack_start(text_view_box, True, True, 0) | |
1441 | self.show_all() | |
1442 | ||
1443 | @scrollable(width=200, height=200) | |
1444 | def create_text_view_box(self, textBuffer): | |
1303 | 1445 | textView = Gtk.TextView() |
1304 | 1446 | textView.set_editable(False) |
1305 | 1447 | textView.set_buffer(textBuffer) |
1306 | ||
1307 | box = self.get_content_area() | |
1308 | scrolled_text = Gtk.ScrolledWindow.new(None, None) | |
1309 | scrolled_text.set_min_content_height(200) | |
1310 | scrolled_text.set_min_content_width(200) | |
1311 | scrolled_text.add(textView) | |
1312 | ||
1313 | box.pack_start(scrolled_text, True, True, 0) | |
1314 | self.show_all() | |
1315 | ||
1316 | ||
1317 | def on_scape_destroy(window, event): | |
1448 | return textView | |
1449 | ||
1450 | ||
1451 | def key_reactions(window, event): | |
1318 | 1452 | """Silly function to destroy a window on escape key, to use |
1319 | 1453 | with all the dialogs that should be Gtk.Dialogs but are Gtk.Windows |
1320 | 1454 | or with windows that are too complex for gtk dialogs but should behave |
1321 | 1455 | as a dialog too""" |
1322 | if event.get_keycode()[1] == 9: | |
1456 | key = Gdk.keyval_name(event.get_keyval()[1]) | |
1457 | if key == 'Escape': | |
1323 | 1458 | window.destroy() |
1324 | 1459 | return True |
1325 | else: | |
1326 | return False | |
1460 | elif key == 'Return': | |
1461 | window.on_click_ok() | |
1462 | return True |
7 | 7 | ''' |
8 | 8 | import gi |
9 | 9 | import os |
10 | import sys | |
11 | 10 | |
12 | 11 | gi.require_version('Gtk', '3.0') |
13 | 12 | gi.require_version('Vte', '2.91') |
19 | 18 | """Defines a simple terminal that will execute faraday-terminal with the |
20 | 19 | corresponding host and port as specified by the CONF""" |
21 | 20 | def __init__(self, CONF): |
21 | """Initialize terminal with infinite scrollback, no bell, connecting | |
22 | all keys presses to copy_or_past, and starting faraday-terminal | |
23 | """ | |
22 | 24 | super(Vte.Terminal, self).__init__() |
23 | 25 | self.set_scrollback_lines(-1) |
24 | 26 | self.set_audible_bell(0) |
53 | 55 | """Decides if the Ctrl+Shift is pressed, in which case returns True. |
54 | 56 | If Ctrl+Shift+C or Ctrl+Shift+V are pressed, copies or pastes, |
55 | 57 | acordingly. Return necesary so it doesn't perform other action, |
56 | like killing the process, on Ctrl+C. | |
58 | like killing the process on Ctrl+C. | |
59 | ||
60 | Note that it won't care about order: Shift+Ctrl+V will work just as | |
61 | Ctrl+Shift+V. | |
57 | 62 | """ |
58 | ||
59 | control_key = Gdk.ModifierType.CONTROL_MASK | |
60 | shift_key = Gdk.ModifierType.SHIFT_MASK | |
63 | control_key = 'control-mask' | |
64 | shift_key = 'shift-mask' | |
65 | last_pressed_key = Gdk.keyval_name(event.get_keyval()[1]) | |
66 | set_pressed_special_keys = set(event.state.value_nicks) | |
61 | 67 | 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 | |
68 | if {control_key, shift_key} <= set_pressed_special_keys: | |
69 | # '<=' means 'is a subset of' in sets | |
70 | if last_pressed_key == 'C': | |
64 | 71 | self.copy_clipboard() |
65 | elif event.keyval == 86: # and that's the V key | |
72 | elif last_pressed_key == 'V': | |
66 | 73 | self.paste_clipboard() |
67 | 74 | return True |
75 | ||
68 | 76 | |
69 | 77 | class Sidebar(Gtk.Notebook): |
70 | 78 | """Defines the bigger sidebar in a notebook. One of its tabs will contain |
88 | 96 | box.pack_start(self, True, True, 0) |
89 | 97 | return box |
90 | 98 | |
99 | ||
91 | 100 | class HostsSidebar(Gtk.Widget): |
92 | 101 | """Defines the widget displayed when the user is in the "Hosts" tab of |
93 | 102 | the Sidebar notebook. Will list all the host, and when clicking on one, |
98 | 107 | almost nothing, the application will inmediatly call create_model |
99 | 108 | with the last workspace and create_view with that model upon startup. |
100 | 109 | """ |
101 | ||
102 | 110 | super(Gtk.Widget, self).__init__() |
103 | 111 | self.open_dialog_callback = open_dialog_callback |
104 | 112 | self.current_model = None |
111 | 119 | host_id in the first column, the icon as a GdkPixbuf.Pixbuf() |
112 | 120 | in the second column and a display_str with the host_name and the |
113 | 121 | 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) | | |
122 | | HOST_ID | HOST_OS_PIXBUF | OS_STR | DISPLAY_STR | VULN_COUNT| | |
123 | ====================================================================== | |
124 | | a923fd | PixBufIcon(linux)| linux | 192.168.1.2 (5) | 5 | | |
117 | 125 | """ |
118 | 126 | def compute_vuln_count(host): |
119 | """Returns the total vulnerability count for a given host""" | |
127 | """Return the total vulnerability count for a given host""" | |
120 | 128 | vuln_count = 0 |
121 | 129 | vuln_count += len(host.getVulns()) |
122 | 130 | for interface in host.getAllInterfaces(): |
123 | 131 | vuln_count += len(interface.getVulns()) |
124 | 132 | for service in interface.getAllServices(): |
125 | 133 | vuln_count += len(service.getVulns()) |
126 | return str(vuln_count) | |
134 | return vuln_count | |
127 | 135 | |
128 | 136 | def decide_icon(os): |
129 | """Decides the correct Pixbuf icon for a OS. None if OS not | |
130 | found or not recognized. | |
137 | """Return the GdkPixbuf icon according to 'os' paramather string | |
138 | and a str_id to that GdkPixbuf for easy comparison and ordering | |
139 | of the view ('os' paramether string is complicated and has caps). | |
131 | 140 | """ |
132 | 141 | os = os.lower() |
133 | 142 | if "linux" in os or "unix" in os: |
134 | 143 | icon = GdkPixbuf.Pixbuf.new_from_file(self.linux_icon) |
144 | str_id = "linux" | |
135 | 145 | elif "windows" in os: |
136 | icon = GdkPixbuf.Pixbuf.new_from_file(self.windows_icon) | |
146 | icon = GdkPixbuf.Pixbuf.new_from_file(self.windows_icon) | |
147 | str_id = "windows" | |
137 | 148 | elif "mac" in os: |
138 | icon = GdkPixbuf.Pixbuf.new_from_file(self.mac_icon) | |
149 | icon = GdkPixbuf.Pixbuf.new_from_file(self.mac_icon) | |
150 | str_id = "mac" | |
139 | 151 | else: |
140 | 152 | icon = None |
141 | return icon | |
142 | ||
143 | hosts_model = Gtk.ListStore(str, GdkPixbuf.Pixbuf(), str) | |
153 | str_id = "unknown" | |
154 | return icon, str_id | |
155 | ||
156 | def compare_os_strings(model, an_os, other_os, user_data): | |
157 | """Compare an_os with other_os so the model knows how to sort them. | |
158 | user_data is not used. | |
159 | Forces 'unknown' OS to be always at the bottom of the model. | |
160 | Return values: | |
161 | 1 means an_os should come after other_os | |
162 | 0 means they are the same | |
163 | -1 means an_os should come before other_os | |
164 | It helps to think about it like the relative position of an_os | |
165 | in respect to other_os (-1 'left' in a list, 1 'right' in a list) | |
166 | """ | |
167 | sort_column = 2 | |
168 | an_os = model.get_value(an_os, sort_column) | |
169 | other_os = model.get_value(other_os, sort_column) | |
170 | if an_os == "unknown": | |
171 | order = 1 | |
172 | elif an_os < other_os or other_os == "unknown": | |
173 | order = -1 | |
174 | elif an_os == other_os: | |
175 | order = 0 | |
176 | else: | |
177 | order = 1 | |
178 | return order | |
179 | ||
180 | hosts_model = Gtk.ListStore(str, GdkPixbuf.Pixbuf(), str, str, int) | |
181 | ||
144 | 182 | for host in hosts: |
145 | 183 | 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 | |
184 | os_icon, os_str = decide_icon(host.getOS()) | |
185 | display_str = host.name + " (" + str(vuln_count) + ")" | |
186 | hosts_model.append([host.id, os_icon, os_str, | |
187 | display_str, vuln_count]) | |
188 | ||
189 | # sort the model by default according to column 4 (num of vulns) | |
190 | sorted_model = Gtk.TreeModelSort(model=hosts_model) | |
191 | sorted_model.set_sort_column_id(4, Gtk.SortType.DESCENDING) | |
192 | ||
193 | # set the sorting function of column 2 | |
194 | sorted_model.set_sort_func(2, compare_os_strings, None) | |
195 | ||
196 | self.current_model = sorted_model | |
197 | ||
198 | return self.current_model | |
151 | 199 | |
152 | 200 | 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. | |
201 | """Creates a view for the hosts model. | |
202 | It will contain two columns, the first with the OS icon given in | |
203 | the second column of the model. The second column of the view will | |
204 | be the string contained in the fourth column of the model. | |
205 | The first column of the view will be orderer according to the | |
206 | second column of the model, and the second column of the view will | |
207 | be ordered according to its fifth column. | |
155 | 208 | Will connect activation of a row with the on_click method |
156 | 209 | """ |
157 | 210 | |
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 | 211 | self.view = Gtk.TreeView(model) |
168 | 212 | self.view.set_activate_on_single_click(True) |
169 | 213 | |
170 | 214 | text_renderer = Gtk.CellRendererText() |
171 | 215 | icon_renderer = Gtk.CellRendererPixbuf() |
172 | 216 | |
173 | column_hosts = Gtk.TreeViewColumn("Hosts", text_renderer, text=2) | |
217 | column_hosts = Gtk.TreeViewColumn("Hosts", text_renderer, text=3) | |
218 | column_hosts.set_sort_column_id(4) | |
219 | column_hosts.set_sort_indicator(True) | |
220 | ||
174 | 221 | column_os = Gtk.TreeViewColumn("", icon_renderer, pixbuf=1) |
222 | column_os.set_sort_column_id(2) | |
223 | column_os.set_sort_indicator(True) | |
175 | 224 | |
176 | 225 | self.view.append_column(column_os) |
177 | 226 | self.view.append_column(column_hosts) |
227 | ||
178 | 228 | |
179 | 229 | self.view.connect("row_activated", self.on_click) |
180 | 230 | |
206 | 256 | scrolled_view.add(self.view) |
207 | 257 | box.pack_start(scrolled_view, True, True, 0) |
208 | 258 | return box |
259 | ||
209 | 260 | |
210 | 261 | class WorkspaceSidebar(Gtk.Widget): |
211 | 262 | """Defines the sidebar widget to be used by the AppWindow, passed as an |
56 | 56 | def workspaceChanged(self, workspace, workspace_type): |
57 | 57 | self._notifyWidgets(events.WorkspaceChangedCustomEvent(workspace,workspace_type)) |
58 | 58 | |
59 | def CouchDBConnectionProblem(self, problem=None): | |
60 | self._notifyWidgets(events.ShowExceptionConnectionRefusedCustomEvent(problem)) | |
61 | ||
59 | 62 | def addHost(self, host): |
60 | 63 | self._notifyWidgets(events.AddHostCustomEvent(host)) |
61 | 64 | |
73 | 76 | |
74 | 77 | def changeFromInstance(self, change): |
75 | 78 | self._notifyWidgets(events.ChangeFromInstanceCustomEvent(change)) |
79 |
15 | 15 | CLEARHOSTS_ID, DIFFHOSTS_ID, SYNCFAILED_ID, |
16 | 16 | CONFLICTS_ID, WORKSPACE_CHANGED, CONFLICT_UPDATE, |
17 | 17 | RESOLVECONFLICTS_ID, UPDATEMODEL_ID, ADDHOST, |
18 | EDITHOST, DELHOST, CHANGEFROMINSTANCE) | |
18 | EDITHOST, DELHOST, CHANGEFROMINSTANCE, | |
19 | CONNECTION_REFUSED) | |
19 | 20 | |
20 | 21 | |
21 | 22 | class LogCustomEvent(qt.QCustomEvent): |
112 | 113 | qt.QCustomEvent.__init__(self, e.type()) |
113 | 114 | self.change = e.change |
114 | 115 | |
116 | class ConnectionRefusedEvent(qt.QCustomEvent): | |
117 | def __init__(self, e): | |
118 | qt.QCustomEvent.__init__(self, e.type()) | |
119 | ||
115 | 120 | |
116 | 121 | class QtCustomEvent(qt.QCustomEvent): |
117 | 122 | events = { |
131 | 136 | ADDHOST: AddHostCustomEvent, |
132 | 137 | DELHOST: DeleteHostCustomEvent, |
133 | 138 | EDITHOST: EditHostCustomEvent, |
134 | CHANGEFROMINSTANCE: ChangeFromInstanceCustomEvent | |
139 | CHANGEFROMINSTANCE: ChangeFromInstanceCustomEvent, | |
140 | CONNECTION_REFUSED: ConnectionRefusedEvent | |
135 | 141 | } |
136 | 142 | |
137 | 143 | @staticmethod |
22 | 22 | |
23 | 23 | |
24 | 24 | class PropertyItem(qt.QListViewItem): |
25 | ||
26 | ||
25 | ||
26 | ||
27 | 27 | """Item for displaying a preferences-set in HostsBrowser.""" |
28 | 28 | def __init__(self, settings, number, parent): |
29 | 29 | """_plugin_settings is the _plugin_settings class to work for |
59 | 59 | qt.QListView.__init__(self, parent) |
60 | 60 | self.setSelectionMode(qt.QListView.Extended) |
61 | 61 | |
62 | ||
63 | ||
64 | ||
65 | ||
66 | ||
62 | ||
63 | ||
64 | ||
65 | ||
66 | ||
67 | 67 | |
68 | 68 | def selectWidget(self, widget): |
69 | 69 | """Find the widget in the list and select it.""" |
70 | 70 | |
71 | ||
72 | ||
71 | ||
72 | ||
73 | 73 | iter = qt.QListViewItemIterator(self) |
74 | 74 | |
75 | 75 | found = None |
106 | 106 | self._model_controller = model_controller |
107 | 107 | |
108 | 108 | self.modelUpdateTimer = qt.QTimer(self) |
109 | ||
109 | ||
110 | 110 | self.__pendingModelObjectRedraws = [] |
111 | 111 | |
112 | 112 | self.reindex_flag_lock = Lock() |
113 | 113 | self.reindex_flag = False |
114 | 114 | |
115 | 115 | self.connect( self.modelUpdateTimer, qt.SIGNAL("timeout()"), self._modelObjectViewUpdater) |
116 | ||
116 | ||
117 | 117 | self.modelUpdateTimer.start( 1000 , False) |
118 | ||
118 | ||
119 | 119 | |
120 | 120 | self.setName(caption) if caption else self.setName("") |
121 | 121 | |
124 | 124 | |
125 | 125 | self._host_items = {} |
126 | 126 | |
127 | ||
127 | ||
128 | 128 | self._category_items = {} |
129 | 129 | |
130 | ||
131 | ||
130 | ||
131 | ||
132 | 132 | self._category_tree = {} |
133 | 133 | |
134 | 134 | self.contextpopups = {} |
136 | 136 | |
137 | 137 | self.contextdispatchers = {} |
138 | 138 | |
139 | ||
139 | ||
140 | 140 | self._filter = "" |
141 | 141 | self.ix = None |
142 | 142 | |
146 | 146 | split.setOrientation(qt.QSplitter.Vertical) |
147 | 147 | |
148 | 148 | lv = self.listview = ModelObjectListView(split) |
149 | ||
150 | ||
151 | ||
149 | ||
150 | ||
151 | ||
152 | 152 | lv.setRootIsDecorated(True) |
153 | 153 | |
154 | ||
154 | ||
155 | 155 | self.connect( lv, qt.SIGNAL("selectionChanged()"), self._itemSelected ) |
156 | 156 | self.connect( lv, qt.SIGNAL("rightButtonPressed(QListViewItem *,const QPoint&,int)"), self._showContextMenu ) |
157 | ||
157 | ||
158 | 158 | lv.addColumn("Hosts") |
159 | 159 | lv.setColumnWidthMode(0,qt.QListView.Maximum) |
160 | ||
161 | ||
162 | ||
160 | ||
161 | ||
162 | ||
163 | 163 | lv.setTreeStepSize(20) |
164 | 164 | |
165 | ||
166 | ||
167 | ||
168 | ||
165 | ||
166 | ||
167 | ||
168 | ||
169 | 169 | self.rootitem = None |
170 | 170 | |
171 | ||
172 | ||
173 | ||
174 | ||
171 | ||
172 | ||
173 | ||
174 | ||
175 | 175 | self.details_table = EditionTable(split) |
176 | 176 | hbox = qt.QHBox(self) |
177 | 177 | self.object_label = qt.QLabel("", hbox) |
181 | 181 | |
182 | 182 | self.prefchilds = [] |
183 | 183 | |
184 | ||
185 | ||
184 | ||
185 | ||
186 | 186 | |
187 | 187 | def load(self, workspace, workspace_type): |
188 | 188 | self.rootitem = WorkspaceListViewItem(self.listview, workspace, workspace_type) |
191 | 191 | def update(self, hosts): |
192 | 192 | self.clearTree() |
193 | 193 | self.redrawTree(hosts) |
194 | ||
194 | ||
195 | 195 | def sizeHint(self): |
196 | 196 | """Returns recommended size of dialog.""" |
197 | 197 | return qt.QSize(70, 200) |
198 | 198 | |
199 | 199 | def resizeEvent (self, event ): |
200 | ||
201 | ||
202 | ||
203 | ||
200 | ||
201 | ||
202 | ||
203 | ||
204 | 204 | self.listview.setColumnWidth(0,self.size().width()-7) |
205 | ||
206 | ||
205 | ||
206 | ||
207 | 207 | |
208 | 208 | def clearTree(self): |
209 | 209 | """ |
281 | 281 | viewall=True |
282 | 282 | hosts=[] |
283 | 283 | |
284 | ||
285 | ||
284 | ||
285 | ||
286 | 286 | |
287 | 287 | for k in self._host_items.keys(): |
288 | ||
288 | ||
289 | 289 | if (self._host_items[k].object.name in hosts) or viewall==True: |
290 | 290 | self._host_items[k].setVisible(True) |
291 | 291 | else: |
293 | 293 | |
294 | 294 | |
295 | 295 | def _filterHost(self,hosts): |
296 | ||
296 | ||
297 | 297 | |
298 | 298 | from whoosh.qparser import QueryParser |
299 | 299 | with self.ix.searcher() as searcher: |
300 | 300 | query = QueryParser("ip", self.ix.schema).parse(self._filter) |
301 | 301 | results = searcher.search(query, limit=None) |
302 | ||
303 | ||
304 | ||
302 | ||
303 | ||
304 | ||
305 | 305 | hostv={} |
306 | 306 | for r in results: |
307 | 307 | hostv[r['ip']]=1 |
415 | 415 | dialog.exec_loop() |
416 | 416 | |
417 | 417 | def _item_save(self): |
418 | ||
419 | ||
418 | ||
419 | ||
420 | 420 | if self._save_callback is not None: |
421 | 421 | self._save_callback() |
422 | 422 | |
427 | 427 | """ |
428 | 428 | this is called when a list view item is selected |
429 | 429 | """ |
430 | ||
430 | ||
431 | 431 | i = self.listview.firstChild() |
432 | 432 | self.items_selected=[] |
433 | 433 | self.items_type={'Host': 0, 'Workspace': 0, 'Service':0, |
447 | 447 | |
448 | 448 | self.itemselected = self.listview.currentItem() |
449 | 449 | |
450 | ||
450 | ||
451 | 451 | self.details_table.clear() |
452 | 452 | editor = self.itemselected.getEditor() |
453 | 453 | editor.fillEditionTable(self.details_table) |
515 | 515 | |
516 | 516 | def _delCategory(self, category, recursive=False): |
517 | 517 | if category in self._category_tree: |
518 | if recursive: | |
518 | if recursive: | |
519 | 519 | for id in self._category_tree: |
520 | 520 | host_item = self._getHostListViewItem(id) |
521 | 521 | if host_item is not None: |
522 | 522 | self._delHostFromCategory(host_item.object, category) |
523 | 523 | else: |
524 | ||
525 | ||
524 | ||
525 | ||
526 | 526 | for id in self._category_tree: |
527 | 527 | host_item = self._getHostListViewItem(id) |
528 | 528 | if host_item is not None: |
540 | 540 | """Pop up a context menu when an item is clicked on the list view.""" |
541 | 541 | ret = None |
542 | 542 | |
543 | if item is not None: | |
544 | ||
545 | ||
546 | ||
547 | ||
543 | if item is not None: | |
544 | ||
545 | ||
546 | ||
547 | ||
548 | 548 | if self.items_type['Interface']: |
549 | 549 | if (self.items_type['Category_General'] or self.items_type['Workspace']): |
550 | 550 | popname="CategoryWorkspace_Interface" |
564 | 564 | else: |
565 | 565 | popname=item.type |
566 | 566 | else: |
567 | ||
567 | ||
568 | 568 | if item.type is "Category": |
569 | 569 | popname=item.type + "_" + item.name |
570 | 570 | else: |
574 | 574 | |
575 | 575 | if ret in self.contextdispatchers: |
576 | 576 | self.contextdispatchers[ret](item) |
577 | ||
578 | ||
577 | ||
578 | ||
579 | 579 | |
580 | 580 | api.devlog("contextMenuEvent - item: %s - ret %s" % (self.name, ret)) |
581 | 581 | |
582 | ||
582 | ||
583 | 583 | |
584 | 584 | def _newHost(self, item): |
585 | 585 | api.devlog("newHost") |
588 | 588 | |
589 | 589 | def _newHostCallback(self, name, os): |
590 | 590 | if name: |
591 | ||
591 | ||
592 | 592 | guiapi.createAndAddHost(name, os=os) |
593 | 593 | |
594 | 594 | def _delHost(self,item): |
595 | 595 | api.devlog("delHost") |
596 | if item is not None and item.object is not None: | |
596 | if item is not None and item.object is not None: | |
597 | 597 | dialog = MessageDialog(self,title="Host delete",callback=self._delSelectedCallback) |
598 | 598 | dialog.exec_loop() |
599 | 599 | |
614 | 614 | |
615 | 615 | def _delInterface(self,item): |
616 | 616 | api.devlog("delInterface") |
617 | if item is not None and item.object is not None: | |
617 | if item is not None and item.object is not None: | |
618 | 618 | dialog = MessageDialog(self,title="Interface delete",callback=self._delSelectedCallback) |
619 | 619 | dialog.exec_loop() |
620 | 620 | |
637 | 637 | guiapi.createAndAddServiceToInterface(host_id, interface_id , name, protocol=protocol, ports=ports) |
638 | 638 | |
639 | 639 | def _delService(self,item): |
640 | if item is not None and item.object is not None: | |
640 | if item is not None and item.object is not None: | |
641 | 641 | dialog = MessageDialog(self,title="Delete Item(s)",callback=self._delSelectedCallback) |
642 | 642 | dialog.exec_loop() |
643 | 643 | |
669 | 669 | parent_interface = self._getParentForType(i, "Interface").object |
670 | 670 | parent_host = self._getParentForType(i, "Host").object |
671 | 671 | guiapi.delServiceFromInterface(parent_host.getID(), parent_interface.getID(), _object.getID()) |
672 | ||
672 | ||
673 | 673 | self.listview.setCurrentItem(self.rootitem) |
674 | 674 | self._itemSelected() |
675 | 675 | |
688 | 688 | |
689 | 689 | def _delCategorymenu(self,item): |
690 | 690 | api.devlog("delCategorymenu") |
691 | if item is not None: | |
691 | if item is not None: | |
692 | 692 | dialog = MessageDialog(self,title="Category delete",callback=self._delCategoryCallback,item=item) |
693 | 693 | dialog.exec_loop() |
694 | 694 | |
734 | 734 | "save file dialog", |
735 | 735 | "Choose a file to save the vulns" ) |
736 | 736 | from exporters.tofile import CSVVulnStatusReport |
737 | CSVVulnStatusReport(path = filename, | |
738 | modelobjects = hosts).createCSVVulnStatusReport() | |
737 | CSVVulnStatusReport(path = filename, | |
738 | modelobjects = hosts).createCSVVulnStatusReport() | |
739 | 739 | |
740 | 740 | def _importVulnsCvs(self,item): |
741 | 741 | filename = qt.QFileDialog.getOpenFileName( |
744 | 744 | None, |
745 | 745 | "open file dialog", |
746 | 746 | "Choose a vulnerability file" ); |
747 | ||
747 | ||
748 | 748 | if os.path.isfile(filename): |
749 | 749 | with open(filename) as f: |
750 | 750 | data = f.read() |
755 | 755 | if re.search("^#",l): |
756 | 756 | api.devlog("ERROR FILE") |
757 | 757 | continue |
758 | ||
758 | ||
759 | 759 | d = l.split("|") |
760 | 760 | if len(d) <8: |
761 | 761 | api.log("Error vuln line: ("+l+")" ) |
775 | 775 | s_id = guiapi.createAndAddServiceToInterface(h_id,i_id,port,protocol,ports=[port]) |
776 | 776 | if type == "2": |
777 | 777 | v_id = guiapi.createAndAddVulnWebToService(h_id,s_id, name, desc, [], severity, "/", "/") |
778 | else: | |
778 | else: | |
779 | 779 | v_id = guiapi.createAndAddVulnToService(h_id,s_id, name, desc, [],severity) |
780 | 780 | |
781 | 781 | api.devlog("type:" + type) |
782 | ||
782 | ||
783 | 783 | def _isIPV4(self, ip): |
784 | 784 | if len(ip.split(".")) == 4: |
785 | 785 | return True |
787 | 787 | return False |
788 | 788 | |
789 | 789 | def _listNotes(self, item): |
790 | if item is not None and item.object is not None: | |
790 | if item is not None and item.object is not None: | |
791 | 791 | dialog = NotesDialog(parent=self, model_object=item.object) |
792 | 792 | dialog.exec_loop() |
793 | 793 | |
794 | 794 | def _newNote(self, item): |
795 | if item is not None and item.object is not None: | |
795 | if item is not None and item.object is not None: | |
796 | 796 | dialog = NewNoteDialog(self, callback=self._newNoteSelectedCallback) |
797 | 797 | dialog.exec_loop() |
798 | 798 | |
831 | 831 | None, |
832 | 832 | "open file dialog", |
833 | 833 | "Choose a password file" ); |
834 | ||
834 | ||
835 | 835 | if os.path.isfile(filename): |
836 | 836 | with open(filename) as f: |
837 | 837 | data = f.read() |
842 | 842 | if re.search("^#",l): |
843 | 843 | api.devlog("ERROR FILE") |
844 | 844 | continue |
845 | ||
845 | ||
846 | 846 | d = l.split(",") |
847 | 847 | if len(d)<=1: |
848 | 848 | d = l.split(":") |
849 | ||
849 | ||
850 | 850 | api.devlog(d) |
851 | 851 | if len(d) <=1: |
852 | 852 | api.devlog("Error password line: ("+l+")" ) |
864 | 864 | d = WorkspacePropertiesDialog(self, "Workspace Properties", workspace=item.object) |
865 | 865 | d.exec_loop() |
866 | 866 | |
867 | def _modelObjectViewUpdater(self): | |
867 | def _modelObjectViewUpdater(self): | |
868 | 868 | if len(self.__pendingModelObjectRedraws): |
869 | 869 | self.update(self.__pendingModelObjectRedraws.pop().hosts) |
870 | 870 | self.__pendingModelObjectRedraws[:] = [] |
909 | 909 | Configures a context popup menu for each kind of item shown in the tree. |
910 | 910 | This is done because different options may be needed for each item |
911 | 911 | """ |
912 | ||
912 | ||
913 | 913 | popup = qt.QPopupMenu(self) |
914 | ||
915 | ||
916 | ||
914 | ||
915 | ||
916 | ||
917 | 917 | popup.insertSeparator() |
918 | 918 | popup.insertItem('Resolve Conflicts', 303) |
919 | 919 | popup.insertItem('Save Vulns CSV', 402) |
920 | 920 | popup.insertItem('Import Vulns CSV', 403) |
921 | ||
922 | ||
921 | ||
922 | ||
923 | 923 | popup.insertSeparator() |
924 | 924 | popup.insertItem('Add Host', 800) |
925 | 925 | |
927 | 927 | |
928 | 928 | self.contextpopups["Category_General"] = self.contextpopups["Workspace"] |
929 | 929 | |
930 | ||
930 | ||
931 | 931 | popup = qt.QPopupMenu(self) |
932 | ||
933 | ||
934 | ||
932 | ||
933 | ||
934 | ||
935 | 935 | |
936 | 936 | self.contextpopups["Category_Applications"] = popup |
937 | 937 | |
938 | ||
938 | ||
939 | 939 | popup = qt.QPopupMenu(self) |
940 | 940 | popup.insertItem('Add Interfaces', 600) |
941 | ||
942 | ||
941 | ||
942 | ||
943 | 943 | |
944 | 944 | self.contextpopups["Category_Interfaces"] = popup |
945 | 945 | |
946 | ||
947 | ||
948 | ||
949 | ||
950 | ||
951 | ||
952 | ||
953 | ||
954 | ||
955 | ||
956 | ||
957 | ||
958 | ||
959 | ||
960 | ||
961 | ||
962 | ||
946 | ||
947 | ||
948 | ||
949 | ||
950 | ||
951 | ||
952 | ||
953 | ||
954 | ||
955 | ||
956 | ||
957 | ||
958 | ||
959 | ||
960 | ||
961 | ||
962 | ||
963 | 963 | popup = qt.QPopupMenu(self) |
964 | 964 | popup.insertItem('Delete Host', 802) |
965 | 965 | popup.insertSeparator() |
974 | 974 | popup.insertItem('New Credential', 550) |
975 | 975 | popup.insertItem('Show Credentials', 551) |
976 | 976 | popup.insertItem('Import Creds', 561) |
977 | ||
978 | ||
977 | ||
978 | ||
979 | 979 | |
980 | 980 | self.contextpopups["Host"] = popup |
981 | 981 | |
982 | ||
982 | ||
983 | 983 | popup = qt.QPopupMenu(self) |
984 | 984 | popup.insertItem('Delete Interface', 602) |
985 | 985 | popup.insertSeparator() |
990 | 990 | popup.insertSeparator() |
991 | 991 | popup.insertItem('New note', 500) |
992 | 992 | popup.insertItem('Show notes', 501) |
993 | ||
994 | ||
993 | ||
994 | ||
995 | 995 | |
996 | 996 | self.contextpopups["Interface"] = popup |
997 | 997 | |
998 | ||
998 | ||
999 | 999 | popup = qt.QPopupMenu(self) |
1000 | 1000 | popup.insertItem('Delete Service', 202) |
1001 | 1001 | popup.insertSeparator() |
1008 | 1008 | popup.insertItem('New Credential', 550) |
1009 | 1009 | popup.insertItem('Show Credentials', 551) |
1010 | 1010 | popup.insertItem('Import Creds', 561) |
1011 | ||
1012 | ||
1011 | ||
1012 | ||
1013 | 1013 | |
1014 | 1014 | self.contextpopups["Service"] = popup |
1015 | 1015 | |
1016 | ||
1017 | ||
1018 | ||
1019 | ||
1020 | ||
1021 | ||
1022 | ||
1023 | ||
1016 | ||
1017 | ||
1018 | ||
1019 | ||
1020 | ||
1021 | ||
1022 | ||
1023 | ||
1024 | 1024 | popup = qt.QPopupMenu(self) |
1025 | 1025 | popup.insertItem('Delete Items', 202) |
1026 | 1026 | popup.insertSeparator() |
1033 | 1033 | |
1034 | 1034 | self.contextpopups["Service_Host"] = popup |
1035 | 1035 | |
1036 | ||
1037 | ||
1036 | ||
1037 | ||
1038 | 1038 | popup = qt.QPopupMenu(self) |
1039 | 1039 | popup.insertItem('Add Service', 200) |
1040 | 1040 | popup.insertSeparator() |
1046 | 1046 | popup.insertSeparator() |
1047 | 1047 | popup.insertItem('New Credential', 550) |
1048 | 1048 | popup.insertItem('Import Creds', 561) |
1049 | ||
1050 | ||
1049 | ||
1050 | ||
1051 | 1051 | |
1052 | 1052 | self.contextpopups["ServiceHost_Interface"] = popup |
1053 | 1053 | |
1054 | ||
1054 | ||
1055 | 1055 | popup = qt.QPopupMenu(self) |
1056 | ||
1057 | ||
1056 | ||
1057 | ||
1058 | 1058 | popup.insertItem('Properties', 302) |
1059 | 1059 | popup.insertSeparator() |
1060 | 1060 | popup.insertItem('Add Host', 800) |
1071 | 1071 | popup.insertSeparator() |
1072 | 1072 | popup.insertItem('New Credential', 550) |
1073 | 1073 | popup.insertItem('Import Creds', 561) |
1074 | ||
1075 | ||
1074 | ||
1075 | ||
1076 | 1076 | self.contextpopups["CategoryWorkspace_Interface"] = popup |
1077 | 1077 | |
1078 | ||
1078 | ||
1079 | 1079 | popup = qt.QPopupMenu(self) |
1080 | ||
1081 | ||
1080 | ||
1081 | ||
1082 | 1082 | popup.insertItem('Properties', 302) |
1083 | 1083 | popup.insertSeparator() |
1084 | 1084 | popup.insertItem('Add Host', 800) |
1091 | 1091 | popup.insertSeparator() |
1092 | 1092 | popup.insertItem('New Credential', 550) |
1093 | 1093 | popup.insertItem('Import Creds', 561) |
1094 | ||
1095 | ||
1094 | ||
1095 | ||
1096 | 1096 | self.contextpopups["CategoryWorkspace_ServiceHost"] = popup |
1097 | 1097 | |
1098 | 1098 | |
1099 | ||
1100 | ||
1101 | ||
1102 | ||
1103 | ||
1104 | ||
1105 | ||
1106 | ||
1099 | ||
1100 | ||
1101 | ||
1102 | ||
1103 | ||
1104 | ||
1105 | ||
1106 | ||
1107 | 1107 | def _setupContextDispatchers(self): |
1108 | 1108 | """ |
1109 | 1109 | Configures a context dispatcher for each kind of item shown in the tree. |
1116 | 1116 | self.contextdispatchers[200] = self._newService |
1117 | 1117 | self.contextdispatchers[202] = self._delService |
1118 | 1118 | |
1119 | ||
1120 | ||
1119 | ||
1120 | ||
1121 | 1121 | self.contextdispatchers[302] = self._showWorkspaceProperties |
1122 | 1122 | self.contextdispatchers[303] = self._resolveConflicts |
1123 | 1123 |
532 | 532 | self.showConflictsDialog(event.local) |
533 | 533 | elif event.type() == CHANGEFROMINSTANCE: |
534 | 534 | self.newNotification(event.change) |
535 | elif event.type() == CONNECTION_REFUSED: | |
536 | self.connectionRefused() | |
537 | ||
538 | def connectionRefused(self): | |
539 | self.showSimpleDialog("The connection to the database was lost. " | |
540 | "Faraday will revert back to the Filesystem. " | |
541 | "Fix your connection to CouchDB and reconnect " | |
542 | "via the preferences dialog") | |
543 | ||
544 | wm = self._main_app.getWorkspaceManager() | |
545 | wm.closeWorkspace() | |
546 | wm.resource() | |
547 | wm.openWorkspace('untitled') | |
548 | ||
549 | mwin = self._main_app.getMainWindow() | |
550 | mwin.getWorkspaceTreeView().loadAllWorkspaces() | |
551 | mwin.getWorkspaceTreeView().setDefaultWorkspace() | |
535 | 552 | |
536 | 553 | def update(self, event): |
537 | 554 | if event.type() == EXCEPTION_ID: |
11 | 11 | fi |
12 | 12 | |
13 | 13 | update=0 |
14 | #protection | |
15 | sha_kali2_i686=d08b0562acc3da5a392509a1801d5569e1ace750d26d020b83ecc4c8eea4f191 | |
16 | sha_kali2_x86_64=f8ee223706bd306dbdba1bd9232f196878c598cb449d006e24edbcbe85f19f2a | |
17 | sha_kali_i686=f071539d8d64ad9b30c7214daf5b890a94b0e6d68f13bdcc34c2453c99afe9c4 | |
18 | sha_kali_x86_64=02a050372fb30ede1454e1dd99d97e0fe0963ce2bd36c45efe90eec78df11d04 | |
19 | sha_ubuntu13_10_i686=8199904fb5fca8bc244c31b596c3ae0d441483bfbb2dc47f66186ceffbf3586e | |
20 | sha_ubuntu13_10_x86_64=2b1af6f8d7463324f6df103455748e53cdb1edf6ee796056cdf2f701ccaef031 | |
21 | sha_ubuntu13_04_i686=d3632a393aa0bf869653afe252248de62e528c4e42ab49a0d16850ab89fda13e | |
22 | sha_ubuntu13_04_x86_64=ea3010b8c3f81229a6b79c8d679005c1d482548223ebc448963d2d29aabe5692 | |
23 | 14 | |
24 | 15 | #os detection |
25 | 16 | arch=$(uname -m) |
39 | 30 | os="$(uname -s) $(uname -r)" |
40 | 31 | fi |
41 | 32 | |
42 | #Check if python2 is already installed | |
43 | if ! which python2 > /dev/null; then | |
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 | |
48 | fi | |
33 | echo "[+] Install $os $arch" | |
49 | 34 | |
50 | echo "[+] Install $os $arch" | |
51 | down=0 | |
52 | if [ "$os" = "Ubuntu 10.04.2 LTS" ]; then | |
53 | version="ubuntu10-04.02$arch" | |
54 | elif [[ "$os" =~ "Kali GNU/Linux 2."*|"Kali GNU/Linux Rolling".* ]]; then | |
55 | version="kali2-$arch" | |
56 | down=1 | |
57 | elif [[ "$os" =~ .*Kali.* ]]; then | |
58 | version="kali-$arch" | |
59 | down=1 | |
60 | elif [[ "$os" =~ "Ubuntu 12.04".* ]]; then | |
61 | version="ubuntu12-$arch" | |
62 | elif [ "$os" = "Ubuntu 13.10" ]; then | |
63 | version="ubuntu13-10-$arch" | |
64 | down=1 | |
65 | elif [ "$os" = "Ubuntu 13.04" ]; then | |
66 | version="ubuntu13-04-$arch" | |
67 | down=1 | |
68 | elif [[ "$os" =~ "Ubuntu 14.04".*|"Ubuntu 14.10".*|"Ubuntu Vivid Vervet (development branch)"|"Ubuntu 15".*|"Ubuntu 16".* ]]; then | |
69 | version="ubuntu13-10-$arch" | |
70 | down=1 | |
71 | # Install pip from github. | |
72 | # Bug: https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1306991 | |
73 | wget https://bootstrap.pypa.io/get-pip.py | |
74 | python get-pip.py | |
75 | elif [[ "$os" =~ "Mint 17".* ]]; then | |
76 | version="ubuntu13-10-$arch" | |
77 | down=1 | |
78 | # Install pip from github. | |
79 | # Bug: https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1306991 | |
80 | wget https://raw.github.com/pypa/pip/master/contrib/get-pip.py | |
81 | python get-pip.py | |
82 | elif [[ "$os" =~ "Debian 7".*|"Debian 8".*|"stretch/sid".* ]]; then | |
83 | version="ubuntu13-10-$arch" | |
84 | down=1 | |
85 | # Install pip from github. | |
86 | # Bug: https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1306991 | |
87 | wget https://bootstrap.pypa.io/get-pip.py | |
88 | python get-pip.py | |
35 | if [[ "$os" =~ "Debian 8".*|"stretch/sid".* ]]; then | |
89 | 36 | |
90 | 37 | #Check if user agree with change to experimental |
91 | 38 | read -r -p "We need change your debian to experimental - sid branch (If you are not). You agree?[Y/n] " input |
101 | 48 | echo "deb http://ftp.debian.org/debian experimental main" >> /etc/apt/sources.list |
102 | 49 | echo "deb http://ftp.debian.org/debian sid main" >> /etc/apt/sources.list |
103 | 50 | apt-get update |
104 | ||
105 | if [[ "$os" =~ "Debian 7".* ]]; then | |
106 | apt-get -t experimental -y install libc6-dev | |
107 | fi | |
108 | else | |
109 | echo "[-] Could not find a install for $os ($arch $kernel)" | |
110 | exit | |
111 | fi | |
112 | ||
113 | if [ "$down" -eq 1 ]; then | |
114 | ||
115 | if [ -e lib-$version.tgz ]; then | |
116 | echo "[+] QT Libs already downloaded" | |
117 | else | |
118 | echo "[+] Download QT Libs" | |
119 | wget "https://www.faradaysec.com/down/faraday/lib-$version.tgz" -O lib-$version.tgz | |
120 | fi | |
121 | ||
122 | shav="sha_${version//-/_}" | |
123 | echo `sha256sum lib-$version.tgz` | |
124 | if [ -e lib-$version.tgz ]; then | |
125 | if [ "`echo ${!shav}`" = "`sha256sum lib-$version.tgz | awk -F\" \" \{'print $1'\}`" ]; then | |
126 | echo "[+] SHA256 ok" | |
127 | tar -xvzf lib-$version.tgz | |
128 | mv lib-$version/ external_libs | |
129 | else | |
130 | rm lib-$version.tgz | |
131 | echo "[-] SHA256 file corrupt, deleted run again ./$0" | |
132 | exit | |
133 | fi | |
134 | else | |
135 | echo "[-] Download error" | |
136 | exit | |
137 | fi | |
138 | else | |
139 | apt-get -y install python-qt3 | |
51 | update=1 | |
140 | 52 | fi |
141 | 53 | |
142 | 54 | if [ "$update" -eq 0 ]; then |
144 | 56 | update=1 |
145 | 57 | fi |
146 | 58 | |
147 | apt-get --ignore-missing -y install ipython python-pip python-dev libpq-dev couchdb | |
59 | apt-get --ignore-missing -y install ipython python-setuptools python-pip python-dev libpq-dev libffi-dev couchdb gir1.2-gtk-3.0 gir1.2-vte-2.91 python-gobject zsh curl | |
148 | 60 | |
149 | #Check if python-setuptools not exists. | |
150 | python -c "import setuptools" > /dev/null 2>&1 | |
151 | ||
152 | if [ "$?" -eq 1 ]; then | |
153 | apt-get install python-setuptools | |
154 | fi | |
155 | ||
156 | #Delete debian experimental from sources. | |
157 | if [[ "$os" =~ "Debian 7".*|"Debian 8".*|"stretch/sid".* ]]; then | |
158 | sed -i 's/deb http:\/\/ftp.debian.org\/debian experimental main//' /etc/apt/sources.list | |
159 | sed -i 's/deb http:\/\/ftp.debian.org\/debian sid main//' /etc/apt/sources.list | |
160 | apt-get update | |
161 | fi | |
162 | ||
163 | pip install -r requirements.txt | |
61 | pip2 install -r requirements.txt | |
164 | 62 | |
165 | 63 | echo "You can now run Faraday, enjoy!" |
38 | 38 | self.active_workspace = None |
39 | 39 | |
40 | 40 | def getWorkspacesNames(self): |
41 | """Returns the names of the workspaces as a list of strings""" | |
41 | 42 | return self.dbManager.getAllDbNames() |
42 | 43 | |
43 | 44 | def createWorkspace(self, name, desc, dbtype=DBTYPE.FS): |
81 | 82 | "For example: " |
82 | 83 | "<couch_uri>http://john:[email protected]:5984</couch_uri>")) |
83 | 84 | except Exception as e: |
84 | raise WorkspaceException(str(e)) | |
85 | return notification_center.CouchDBConnectionProblem(e) | |
86 | #raise WorkspaceException(str(e)) | |
85 | 87 | self.mappersManager.createMappers(dbConnector) |
86 | 88 | workspace = self.mappersManager.getMapper( |
87 | 89 | Workspace.__name__).find(name) |
8 | 8 | from config.configuration import getInstanceConfiguration |
9 | 9 | from model.common import ModelObject, ModelObjectNote, ModelObjectVuln, ModelObjectVulnWeb, ModelObjectCred, ModelComposite, ModelLeaf |
10 | 10 | from model.common import Metadata |
11 | from utils.common import * | |
11 | from utils.common import * | |
12 | 12 | from utils.decorators import updateLocalMetadata |
13 | 13 | |
14 | 14 | import model.api as api |
27 | 27 | The host has some attributes that are filled by the pen test tools run by |
28 | 28 | the user |
29 | 29 | """ |
30 | ||
30 | ||
31 | 31 | class_signature = "Host" |
32 | 32 | |
33 | 33 | def __init__(self, name, os = "Unknown", default_gateway=None, dic=None, parent_id=None): |
50 | 50 | if default_gateway is None else default_gateway |
51 | 51 | |
52 | 52 | def _updatePublicAttributes(self): |
53 | ||
53 | ||
54 | 54 | self.publicattrs['Operating System'] = 'getOS' |
55 | 55 | self.publicattrsrefs['Operating System'] = '_operating_system' |
56 | 56 | |
58 | 58 | """ Accept method for visitor in the host leaf""" |
59 | 59 | for ints in self.getAllInterfaces(): |
60 | 60 | ints.accept(visitor) |
61 | visitor.visit(self) | |
61 | visitor.visit(self) | |
62 | 62 | |
63 | 63 | def getCategories(self): |
64 | 64 | return self.categories |
65 | 65 | |
66 | 66 | def getCurrentCategory(self): |
67 | ||
67 | ||
68 | 68 | cat = CONF.getDefaultCategory() |
69 | 69 | try: |
70 | 70 | cat = self.getCategories()[0] |
72 | 72 | pass |
73 | 73 | return cat |
74 | 74 | |
75 | def updateID(self): | |
75 | def updateID(self): | |
76 | 76 | self._id = get_hash([self._name]) |
77 | 77 | self._prependParentId() |
78 | 78 | |
79 | def setOS(self, newOS): | |
79 | def setOS(self, newOS): | |
80 | 80 | self._operating_system = newOS |
81 | 81 | |
82 | 82 | def getOS(self): |
83 | 83 | return self._operating_system |
84 | ||
84 | ||
85 | 85 | operating_system = property(getOS, setOS) |
86 | 86 | |
87 | 87 | def setName(self, newName): |
88 | ||
88 | ||
89 | 89 | self._name = newName |
90 | 90 | |
91 | 91 | def getName(self): |
92 | 92 | return self._name |
93 | ||
93 | ||
94 | 94 | name = property(getName, setName) |
95 | 95 | |
96 | 96 | def getDefaultGateway(self): |
98 | 98 | |
99 | 99 | def setDefaultGateway(self, default_gateway): |
100 | 100 | self._default_gateway = default_gateway |
101 | ||
101 | ||
102 | 102 | |
103 | 103 | #@save |
104 | 104 | @updateLocalMetadata |
118 | 118 | def getAllInterfaces(self, mode = 0): |
119 | 119 | return self.getChildsByType(Interface.__name__) |
120 | 120 | |
121 | def getInterface(self, value): | |
122 | """ | |
123 | value can be mac or ip address | |
124 | if value is found it returns the interface objets | |
125 | it returns None otherwise | |
126 | """ | |
127 | return self.childs.get(value, None) | |
128 | ||
121 | def getInterface(self, ID): | |
122 | """Return the interface of id ID, None if ID wasn't an interface or | |
123 | wasn't found among the children. | |
124 | """ | |
125 | interface = self.findChild(ID) | |
126 | return interface if interface.class_signature == "Interface" else None | |
129 | 127 | |
130 | 128 | def getService(self, name): |
131 | 129 | """ |
146 | 144 | |
147 | 145 | @updateLocalMetadata |
148 | 146 | def delApplication(self, appID): # Deprecated |
149 | ||
147 | ||
150 | 148 | app = self.getApplication(appID) |
151 | 149 | if app is not None: |
152 | 150 | for srv in app.getAllServices(): |
153 | 151 | srv.delApplication(appID) |
154 | ||
152 | ||
155 | 153 | return self._delValue("_applications", appID) |
156 | 154 | |
157 | 155 | def addApplicationFull(self, app): # Deprecated # Deprecated |
177 | 175 | if self._name == other_host.getName(): |
178 | 176 | return True |
179 | 177 | else: |
180 | ||
178 | ||
181 | 179 | ip_addr_this = self.getIPv4Addresses() |
182 | 180 | ip_addr_other = other_host.getIPv4Addresses() |
183 | ||
184 | ||
185 | ||
186 | ||
187 | ||
188 | ||
189 | ||
190 | ||
191 | ||
192 | ||
181 | ||
182 | ||
183 | ||
184 | ||
185 | ||
186 | ||
187 | ||
188 | ||
189 | ||
190 | ||
193 | 191 | for addr in ip_addr_this: |
194 | 192 | if addr in ip_addr_other and IPy.IP(addr).iptype() == "PUBLIC": |
195 | 193 | return True |
196 | ||
194 | ||
197 | 195 | return False |
198 | 196 | |
199 | def __ne__(self, other_host): | |
197 | def __ne__(self, other_host): | |
200 | 198 | return not self == other_host |
201 | 199 | |
202 | 200 | def getIPv4Addresses(self): |
219 | 217 | """ |
220 | 218 | An interface in a host |
221 | 219 | """ |
222 | ||
220 | ||
223 | 221 | class_signature = "Interface" |
224 | 222 | |
225 | 223 | def __init__(self, name = "", mac = "00:00:00:00:00:00", |
231 | 229 | |
232 | 230 | ModelComposite.__init__(self, parent_id) |
233 | 231 | |
234 | ||
232 | ||
235 | 233 | self._name = name |
236 | 234 | self.mac = mac |
237 | 235 | self.ipv4 = { |
240 | 238 | "gateway" : ipv4_gateway, |
241 | 239 | "DNS" : ipv4_dns |
242 | 240 | } |
243 | ||
241 | ||
244 | 242 | self.ipv6 = { |
245 | 243 | "address" : ipv6_address, |
246 | 244 | "prefix" : ipv6_prefix, |
248 | 246 | "DNS" : ipv6_dns |
249 | 247 | } |
250 | 248 | |
251 | ||
249 | ||
252 | 250 | self._services = {} |
253 | 251 | |
254 | ||
252 | ||
255 | 253 | self.network_segment = network_segment |
256 | 254 | |
257 | ||
255 | ||
258 | 256 | self._hostnames=[] |
259 | 257 | if hostname_resolution is not None: |
260 | 258 | if isinstance(hostname_resolution, (str,unicode)): |
267 | 265 | self.amount_ports_filtered = 0 |
268 | 266 | |
269 | 267 | def _updatePublicAttributes(self): |
270 | ||
268 | ||
271 | 269 | self.publicattrs['MAC Address'] = 'mac' |
272 | 270 | self.publicattrs['IPV4 Settings'] = 'ipv4' |
273 | 271 | self.publicattrs['IPV6 Settings'] = 'ipv6' |
296 | 294 | def accept(self, visitor): |
297 | 295 | for servs in self.getAllServices(): |
298 | 296 | servs.accept(visitor) |
299 | visitor.visit(self) | |
300 | ||
301 | ||
297 | visitor.visit(self) | |
298 | ||
299 | ||
302 | 300 | def tieBreakable(self, property_key): |
303 | 301 | if property_key in ["_hostnames"]: |
304 | 302 | return True |
310 | 308 | return list(set(prop1)) |
311 | 309 | return None |
312 | 310 | |
313 | def updateID(self): | |
311 | def updateID(self): | |
314 | 312 | self._id = get_hash([self.network_segment, self.ipv4["address"], self.ipv6["address"]]) |
315 | 313 | self._prependParentId() |
316 | 314 | |
317 | 315 | def setName(self, name): |
318 | 316 | self._name = name |
319 | ||
317 | ||
320 | 318 | def getName(self): |
321 | 319 | return self._name |
322 | ||
323 | ||
320 | ||
321 | ||
324 | 322 | |
325 | 323 | def setMAC(self, mac): |
326 | 324 | self.mac = mac |
327 | ||
325 | ||
328 | 326 | def getMAC(self): |
329 | 327 | return self.mac |
330 | ||
328 | ||
331 | 329 | def setNetworkSegment(self, network_segment): |
332 | 330 | self.network_segment = network_segment |
333 | ||
331 | ||
334 | 332 | def getNetworkSegment(self): |
335 | return self.network_segment | |
336 | ||
333 | return self.network_segment | |
334 | ||
337 | 335 | def setIPv4(self, ipv4): |
338 | 336 | self.ipv4["address"] = ipv4.get("address", None) |
339 | 337 | self.ipv4["mask"] = ipv4.get("mask", None) |
340 | 338 | self.ipv4["gateway"] = ipv4.get("gateway", None) |
341 | 339 | self.ipv4["DNS"] = ipv4.get("DNS", None) |
342 | ||
340 | ||
343 | 341 | def getIPv4(self): |
344 | 342 | return self.ipv4 |
345 | 343 | |
354 | 352 | |
355 | 353 | def getIPv4DNS(self): |
356 | 354 | return self.ipv4["DNS"] |
357 | ||
355 | ||
358 | 356 | def setIPv6(self, ipv6): |
359 | 357 | self.ipv6["address"] = ipv6.get("address", None) |
360 | 358 | self.ipv6["prefix"] = ipv6.get("prefix", None) |
361 | 359 | self.ipv6["gateway"] = ipv6.get("gateway", None) |
362 | 360 | self.ipv6["DNS"] = ipv6.get("DNS", None) |
363 | ||
361 | ||
364 | 362 | def getIPv6(self): |
365 | 363 | return self.ipv6 |
366 | 364 | |
375 | 373 | |
376 | 374 | def getIPv6DNS(self): |
377 | 375 | return self.ipv6["DNS"] |
378 | ||
376 | ||
379 | 377 | def setPortsOpened(self, ports_opened): |
380 | 378 | self.amount_ports_opened = ports_opened |
381 | ||
379 | ||
382 | 380 | def getPortsOpened(self): |
383 | return self.amount_ports_opened | |
384 | ||
381 | return self.amount_ports_opened | |
382 | ||
385 | 383 | def setPortsClosed(self, ports_closed): |
386 | 384 | self.amount_ports_closed = ports_closed |
387 | ||
385 | ||
388 | 386 | def getPortsClosed(self): |
389 | return self.amount_ports_closed | |
390 | ||
387 | return self.amount_ports_closed | |
388 | ||
391 | 389 | def setPortsFiltered(self, ports_filtered): |
392 | 390 | self.amount_ports_filtered = ports_filtered |
393 | ||
391 | ||
394 | 392 | def getPortsFiltered(self): |
395 | 393 | return self.amount_ports_filtered |
396 | 394 | |
397 | 395 | @updateLocalMetadata |
398 | 396 | def addService(self, newService, update=False, setparent=True): # Deprecated |
399 | res = self._addValue("_services", newService, setparent=setparent, update=update) | |
397 | res = self._addValue("_services", newService, setparent=setparent, update=update) | |
400 | 398 | if res: newService.addInterface(self) |
401 | 399 | return res |
402 | 400 | |
409 | 407 | def getAllServices(self, mode = 0): |
410 | 408 | return self.getChildsByType(Service.__name__) |
411 | 409 | |
412 | def getService(self, name): | |
413 | """ | |
414 | if name is found it returnsnetwork_segment the service object | |
415 | it returns None otherwise | |
416 | """ | |
417 | return self._getValueByID("_services", name) | |
410 | def getService(self, ID): | |
411 | """Get a Service from an ID. Return the service object if found, | |
412 | None if ID wasn't a service or wasn't found among the children. | |
413 | """ | |
414 | service = self.findChild(ID) | |
415 | return service if service.class_signature == "Service" else None | |
418 | 416 | |
419 | 417 | def setServices(self, services): |
420 | 418 | self._addChildsDict(services) |
429 | 427 | |
430 | 428 | def getHostnames(self): |
431 | 429 | return self._hostnames |
432 | ||
430 | ||
433 | 431 | def setHostnames(self, hostnames): |
434 | 432 | self._hostnames = hostnames |
435 | 433 | |
469 | 467 | Commonly a service will have a name or description, a set of ports in which |
470 | 468 | is listening and also a particular version |
471 | 469 | """ |
472 | ||
470 | ||
473 | 471 | class_signature = "Service" |
474 | 472 | |
475 | 473 | def __init__(self, name, protocol="TCP", ports=None, status="running", |
488 | 486 | self._creds = {} |
489 | 487 | |
490 | 488 | def _updatePublicAttributes(self): |
491 | ||
489 | ||
492 | 490 | self.publicattrsrefs['Ports'] = '_ports' |
493 | 491 | self.publicattrsrefs['Protocol'] = '_protocol' |
494 | 492 | self.publicattrsrefs['Status'] = '_status' |
502 | 500 | |
503 | 501 | def setName(self, name): |
504 | 502 | self._name = name |
505 | ||
503 | ||
506 | 504 | def getName(self): |
507 | 505 | return self._name |
508 | 506 | |
523 | 521 | |
524 | 522 | def getPorts(self): |
525 | 523 | return self._ports |
526 | ||
524 | ||
527 | 525 | def setPorts(self, ports): |
528 | 526 | if ports is not None: |
529 | 527 | if isinstance(ports, (str,unicode)): |
547 | 545 | def getVersion(self): |
548 | 546 | return self._version |
549 | 547 | |
550 | def updateID(self): | |
548 | def updateID(self): | |
551 | 549 | self._id = get_hash([self._protocol, ":".join(str(self._ports))]) |
552 | 550 | self._prependParentId() |
553 | 551 | |
554 | 552 | #@save |
555 | 553 | @updateLocalMetadata |
556 | def updateAttributes(self, name=None, description=None, protocol=None, ports=None, | |
554 | def updateAttributes(self, name=None, description=None, protocol=None, ports=None, | |
557 | 555 | status=None, version=None, owned=None): |
558 | 556 | if name is not None: |
559 | 557 | self.setName(name) |
560 | 558 | if description is not None: |
561 | 559 | self.setDescription(description) |
562 | 560 | if protocol is not None: |
563 | self.setProtocol(protocol) | |
561 | self.setProtocol(protocol) | |
564 | 562 | if ports is not None: |
565 | 563 | self.setPorts(ports) |
566 | 564 | if status is not None: |
568 | 566 | if version is not None: |
569 | 567 | self.setVersion(version) |
570 | 568 | if owned is not None: |
571 | self.setOwned(owned) | |
569 | self.setOwned(owned) | |
572 | 570 | |
573 | 571 | def _checkFullDelete(self): |
574 | 572 | api.devlog("Doing service checkFullDelete") |
584 | 582 | """ |
585 | 583 | return self._getAllValues("_interfaces", mode) |
586 | 584 | |
587 | def getInterface(self, value): | |
588 | """ | |
589 | value can be a mac or ipv4 address | |
590 | if value is found it returns the interface objets | |
591 | it returns None otherwise | |
592 | """ | |
593 | return self._getValueByID("_interfaces", value) | |
585 | def getInterface(self, ID): | |
586 | """Gets the interface with id ID. If ID isn't found or isn't an | |
587 | interface, return None. | |
588 | """ | |
589 | interface = self.findChild(ID) | |
590 | return interface if interface.class_signature == "Interface" else None | |
594 | 591 | |
595 | 592 | def addApplication(self, newApp, update=False): # Deprecated |
596 | 593 | res = self._addValue("_applications", newApp, update=update) |
628 | 625 | The application can be related to more than one service |
629 | 626 | Commonly this will have a name, description, version and status |
630 | 627 | """ |
631 | ||
628 | ||
632 | 629 | class_signature = "HostApplication" |
633 | 630 | |
634 | 631 | def __init__(self, name, status = "running", version = "unknonw"): |
637 | 634 | self._name = name |
638 | 635 | self._status = status |
639 | 636 | self._version = version |
640 | ||
637 | ||
641 | 638 | self._services = {} |
642 | 639 | |
643 | def _updatePublicAttributes(self): | |
640 | def _updatePublicAttributes(self): | |
644 | 641 | self.publicattrs['Status'] = 'getStatus' |
645 | 642 | self.publicattrs['Version'] = 'getVersion' |
646 | 643 | |
647 | 644 | def setName(self, name): |
648 | 645 | self._name = name |
649 | ||
646 | ||
650 | 647 | def getName(self): |
651 | return self._name | |
652 | ||
648 | return self._name | |
649 | ||
653 | 650 | def setStatus(self, status): |
654 | 651 | self._status = status |
655 | 652 | |
665 | 662 | def updateID(self): |
666 | 663 | self._id = get_hash([self._name, self._version]) |
667 | 664 | self._prependParentId() |
668 | ||
665 | ||
669 | 666 | @updateLocalMetadata |
670 | 667 | def updateAttributes(self, name=None, description=None, status=None, version=None, owned=None): |
671 | 668 | if name is not None: |
704 | 701 | """ |
705 | 702 | return self._getAllValues("_services", mode) |
706 | 703 | |
707 | def getService(self, name): | |
704 | def getService(self, ID): | |
708 | 705 | """ |
709 | 706 | if name is found it returns the service object |
710 | 707 | it returns None otherwise |
36 | 36 | } |
37 | 37 | |
38 | 38 | def unserialize(self, mobj, doc): |
39 | mobj_type = mobj.class_signature | |
39 | 40 | self.children = self.findChildren(mobj.getID()) |
40 | 41 | mobj.setName(doc.get("name")) |
41 | 42 | mobj.setOwned(doc.get("owned")) |
42 | 43 | if doc.get("parent", None): |
43 | 44 | mobj.setParent(self.mapper_manager.find(doc.get("parent"))) |
44 | 45 | mobj.setOwner(doc.get("owner")) |
45 | mobj.setDescription(doc.get("description")) | |
46 | # NOTE: Vulnerability and VulnerabilityWeb, when modified from the web, | |
47 | # have a 'desc' key, not a description key, which is already handled | |
48 | # by their specific unserialize method | |
49 | if mobj_type != 'Vulnerability' and mobj_type != 'VulnerabilityWeb': | |
50 | mobj.setDescription(doc.get("description")) | |
46 | 51 | mobj.setMetadata( Metadata('').fromDict(mobj.getMetadata().__dict__)) |
47 | 52 | if self.children: |
48 | 53 | self.setNotes(mobj) |
265 | 270 | return doc |
266 | 271 | |
267 | 272 | def unserialize(self, vuln_web, doc): |
273 | vuln_web.setDesc(doc.get("desc")) | |
268 | 274 | vuln_web.setWebsite(doc.get("website")) |
269 | 275 | vuln_web.setPath(doc.get("path")) |
270 | 276 | vuln_web.setRequest(doc.get("request")) |
10 | 10 | import mockito |
11 | 11 | import restkit |
12 | 12 | import threading |
13 | import requests | |
14 | import time | |
13 | 15 | from urlparse import urlparse |
14 | 16 | import traceback |
15 | 17 | from couchdbkit import Server, ChangesStream, Database |
21 | 23 | #from persistence.change import change_factory |
22 | 24 | from config.globals import CONST_BLACKDBS |
23 | 25 | from config.configuration import getInstanceConfiguration |
26 | ||
24 | 27 | CONF = getInstanceConfiguration() |
25 | 28 | |
26 | 29 | |
131 | 134 | |
132 | 135 | def setChangesCallback(self, callback): |
133 | 136 | self.changes_callback = callback |
137 | ||
138 | def setCouchExceptionCallback(self, callback): | |
139 | self.couch_exception_callback = callback | |
134 | 140 | |
135 | 141 | def waitForDBChange(self): |
136 | 142 | pass |
234 | 240 | getLogger(self).warn( |
235 | 241 | "You're not authorized to upload views to this database") |
236 | 242 | self.seq_num = self.db.info()['update_seq'] |
243 | test_couch_thread = threading.Thread(target=self.continuosly_check_connection) | |
244 | test_couch_thread.daemon = True | |
245 | test_couch_thread.start() | |
237 | 246 | |
238 | 247 | def getDocs(self): |
239 | 248 | if len(self._docs.keys()) == 0: |
290 | 299 | return res |
291 | 300 | |
292 | 301 | def forceUpdate(self): |
302 | """It will try to update the information on the DB if it can. | |
303 | The except clause is necesary to catch the case where we've lost | |
304 | the connection to the DB. | |
305 | """ | |
306 | ||
293 | 307 | doc = self.getDocument(self.db.dbname) |
294 | return self.db.save_doc(doc, use_uuids=True, force_update=True) | |
308 | try: | |
309 | return self.db.save_doc(doc, use_uuids=True, force_update=True) | |
310 | except: | |
311 | return False | |
295 | 312 | |
296 | 313 | #@trap_timeout |
297 | 314 | def getDocument(self, document_id): |
345 | 362 | def setSeqNumber(self, seq_num): |
346 | 363 | self.seq_num = seq_num |
347 | 364 | |
365 | def continuosly_check_connection(self): | |
366 | """Intended to use on a separate thread. Call module-level | |
367 | function testCouch every second to see if response to the server_uri | |
368 | of the DB is still 200. Call the exception_callback if we can't access | |
369 | the server three times in a row. | |
370 | """ | |
371 | tolerance = 0 | |
372 | server_uri = self.db.server_uri | |
373 | while True: | |
374 | time.sleep(1) | |
375 | test_was_successful = test_couch(server_uri) | |
376 | if test_was_successful: | |
377 | tolerance = 0 | |
378 | else: | |
379 | tolerance += 1 | |
380 | if tolerance == 3: | |
381 | self.couch_exception_callback() | |
382 | return False # kill the thread if something went wrong | |
383 | ||
348 | 384 | #@trap_timeout |
349 | 385 | def waitForDBChange(self, since=0): |
386 | """Listen to the stream of changes provided by CouchDbKit. Process | |
387 | these changes accordingly. If there's an exception while listening | |
388 | to the changes, return inmediatly.""" | |
389 | ||
390 | # XXX: the while True found here shouldn't be necessary because | |
391 | # changesStream already keeps listening 'for ever'. In a few tests | |
392 | # I ran, this hypothesis was confirmed, but with our current setup | |
393 | # i'm afraid I may be missing something. In any case, it works | |
394 | # as it is, but this definitevely needs revision. | |
395 | ||
350 | 396 | getLogger(self).debug( |
351 | 397 | "Watching for changes") |
352 | 398 | while True: |
377 | 423 | except Exception as e: |
378 | 424 | getLogger(self).info("Some exception happened while waiting for changes") |
379 | 425 | getLogger(self).info(" The exception was: %s" % e) |
426 | return False # kill thread, it's failed... in reconnection | |
427 | # another one will be created, don't worry | |
380 | 428 | |
381 | 429 | #@trap_timeout |
382 | 430 | def _compactDatabase(self): |
467 | 515 | |
468 | 516 | |
469 | 517 | class NoCouchDBError(Exception): |
470 | pass | |
518 | def __init__(self): | |
519 | Exception.__init__(self, "NoCouchDBError") | |
471 | 520 | |
472 | 521 | |
473 | 522 | class NoConectionServer(object): |
518 | 567 | getLogger(self).warn("No route to couchdb server on: %s" % uri) |
519 | 568 | getLogger(self).debug(traceback.format_exc()) |
520 | 569 | |
570 | ||
521 | 571 | #@trap_timeout |
522 | 572 | def _create(self, name): |
523 | 573 | db = self.__serv.create_db(name.lower()) |
530 | 580 | #@trap_timeout |
531 | 581 | def _loadDbs(self): |
532 | 582 | conditions = lambda x: not x.startswith("_") and x not in CONST_BLACKDBS |
533 | for dbname in filter(conditions, self.__serv.all_dbs()): | |
534 | if dbname not in self.dbs.keys(): | |
535 | getLogger(self).debug( | |
536 | "Asking for dbname[%s], registering for lazy initialization" % dbname) | |
537 | self.dbs[dbname] = lambda x: self._loadDb(x) | |
583 | try: | |
584 | for dbname in filter(conditions, self.__serv.all_dbs()): | |
585 | if dbname not in self.dbs.keys(): | |
586 | getLogger(self).debug( | |
587 | "Asking for dbname[%s], registering for lazy initialization" % dbname) | |
588 | self.dbs[dbname] = lambda x: self._loadDb(x) | |
589 | except restkit.errors.RequestError as req_error: | |
590 | getLogger(self).error("Couldn't load databases. " | |
591 | "The connection to the CouchDB was probably lost. ") | |
538 | 592 | |
539 | 593 | def _loadDb(self, dbname): |
540 | 594 | db = self.__serv.get_db(dbname) |
542 | 596 | self.dbs[dbname] = CouchDbConnector(db, seq_num=seq) |
543 | 597 | return self.dbs[dbname] |
544 | 598 | |
599 | def refreshDbs(self): | |
600 | """Refresh databases using inherited method. On exception, asume | |
601 | no databases are available. | |
602 | """ | |
603 | try: | |
604 | return AbstractPersistenceManager.refreshDbs() | |
605 | except: | |
606 | return [] | |
545 | 607 | |
546 | 608 | #@trap_timeout |
547 | 609 | def pushReports(self): |
573 | 635 | |
574 | 636 | @staticmethod |
575 | 637 | def testCouch(uri): |
576 | if uri is not None: | |
577 | host, port = None, None | |
578 | try: | |
579 | import socket | |
580 | url = urlparse(uri) | |
581 | proto = url.scheme | |
582 | host = url.hostname | |
583 | port = url.port | |
584 | ||
585 | port = port if port else socket.getservbyname(proto) | |
586 | s = socket.socket() | |
587 | s.settimeout(1) | |
588 | s.connect((host, int(port))) | |
589 | except: | |
590 | return False | |
591 | #getLogger(CouchdbManager).info("Connecting Couch to: %s:%s" % (host, port)) | |
592 | return True | |
638 | """Redirect to the module-level function of the name, which | |
639 | serves the same purpose and is used by other classes too.""" | |
640 | return test_couch(uri) | |
593 | 641 | |
594 | 642 | def testCouchUrl(self, uri): |
595 | 643 | if uri is not None: |
628 | 676 | self.__serv.replicate(workspace, dst, mutual = mutual, continuous = continuous, create_target = ct) |
629 | 677 | if mutual: |
630 | 678 | self.__serv.replicate(dst, src, continuous = continuous, **kwargs) |
679 | ||
680 | ||
681 | def test_couch(uri): | |
682 | """Return True if we could access uri/_all_dbs, which should happen | |
683 | if we have an Internet connection, Couch is up and we have the correct | |
684 | permissions (response_code == 200) | |
685 | """ | |
686 | try: | |
687 | response_code = requests.get(uri + '/_all_dbs').status_code | |
688 | return True if response_code == 200 else False | |
689 | except requests.adapters.ConnectionError: | |
690 | return False |
0 | ''' | |
1 | Faraday Penetration Test IDE | |
2 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
3 | See the file 'doc/LICENSE' for the license information | |
4 | ||
5 | ''' | |
6 |
0 | #!/usr/bin/env python | |
1 | # -*- coding: utf-8 -*- | |
2 | ||
3 | ''' | |
4 | Faraday Penetration Test IDE | |
5 | Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/) | |
6 | See the file 'doc/LICENSE' for the license information | |
7 | ||
8 | ''' | |
9 | from plugins import core | |
10 | from model import api | |
11 | import re | |
12 | import os | |
13 | import socket | |
14 | ||
15 | __author__ = "Ulisses Albuquerque" | |
16 | __copyright__ = "Copyright (c) 2016, Securus Global" | |
17 | __credits__ = ["Ulisses Albuquerque"] | |
18 | __license__ = "" | |
19 | __version__ = "1.0.0" | |
20 | __maintainer__ = "Ulisses Albuquerque" | |
21 | __email__ = "[email protected]" | |
22 | __status__ = "Development" | |
23 | ||
24 | ||
25 | class CmdNetcatPlugin(core.PluginBase): | |
26 | """ | |
27 | This plugin handles ping command. | |
28 | Basically detects if user was able to connect to a device | |
29 | """ | |
30 | def __init__(self): | |
31 | core.PluginBase.__init__(self) | |
32 | self.id = "netcat" | |
33 | self.name = "Netcat" | |
34 | self.plugin_version = "0.0.1" | |
35 | self.version = "1.0.0" | |
36 | self._command_regex = re.compile(r'^(?:.*\|)?\s*(?:nc|netcat|nc.openbsd|nc.traditional)\s+.*$') | |
37 | self._completition = { | |
38 | "": "[-bhklnrtuvCz] [-c shell] [-e filename] [-g gateway] [-G num] [-i secs] [-o file] [-p port] [-q secs] [-s addr] [-T tos] [-w secs]", | |
39 | "-c": "shell", | |
40 | "-e": "filename", | |
41 | "-b": "allow broadcasts", | |
42 | "-g": "gateway", | |
43 | "-G": "num", | |
44 | "-h": "this cruft", | |
45 | "-i": "secs", | |
46 | "-k": "set keepalive option on socket", | |
47 | "-l": "listen mode, for inbound connects", | |
48 | "-n": "numeric-only IP addresses, no DNS", | |
49 | "-o": "file", | |
50 | "-p": "port", | |
51 | "-r": "randomize local and remote ports", | |
52 | "-q": "secs", | |
53 | "-s": "addr", | |
54 | "-T": "tos", | |
55 | "-t": "answer TELNET negotiation", | |
56 | "-u": "UDP mode", | |
57 | "-v": "verbose [use twice to be more verbose]", | |
58 | "-w": "secs", | |
59 | "-C": "Send CRLF as line-ending", | |
60 | "-z": "zero-I/O mode [used for scanning]", | |
61 | } | |
62 | ||
63 | def resolveHost(self, host): | |
64 | """ | |
65 | The use of gethostbyname/gethostbyaddr here is questionable, but it is | |
66 | the easiest way to sort out the discrepancies between the output | |
67 | formats of both versions of netcat | |
68 | """ | |
69 | if re.search(r'^\d{1,3}(?:\.\d{1,3}){3}', host) is not None: | |
70 | try: | |
71 | result = socket.gethostbyaddr(host) | |
72 | return (host, result[0]) | |
73 | except: | |
74 | return (host, None) | |
75 | else: | |
76 | try: | |
77 | result = socket.gethostbyname(host) | |
78 | return (result, host) | |
79 | except: | |
80 | return (None, host) | |
81 | ||
82 | def addEntry(self, attr_dict): | |
83 | """ | |
84 | Because output differs between both versions of netcat, and because | |
85 | the user might use the -n parameter which disables name resolution, | |
86 | we need to check if the values we are getting are hostnames or IP | |
87 | addresses | |
88 | """ | |
89 | ip_address, hostname = self.resolveHost(attr_dict['host']) | |
90 | ||
91 | # When service does not match anything in /etc/services, we get those | |
92 | if attr_dict['service'] == '*' or attr_dict['service'] == '?' or attr_dict['service'] is None: | |
93 | attr_dict['service'] = 'unknown' | |
94 | ||
95 | if 'protocol' not in attr_dict: | |
96 | attr_dict['protocol'] = 'tcp' | |
97 | ||
98 | h_id = self.createAndAddHost(hostname) | |
99 | i_id = self.createAndAddInterface(h_id, ip_address, ipv4_address = ip_address) | |
100 | s_id = self.createAndAddServiceToInterface(h_id, i_id, attr_dict['service'], | |
101 | protocol = attr_dict['protocol'], ports = [ int(attr_dict['port']) ]) | |
102 | ||
103 | def matchInOutput(self, regexp, output): | |
104 | """ | |
105 | We take a split & filter approach to matching our regexps to the | |
106 | command output | |
107 | """ | |
108 | mapped_list = map(lambda s: re.search(regexp, s), re.split(r'(\r|\n)', output)) | |
109 | filtered_list = filter(lambda s: s is not None, mapped_list) | |
110 | ||
111 | if len(filtered_list) > 0: | |
112 | return filtered_list[0] | |
113 | else: | |
114 | return None | |
115 | ||
116 | def parseOutputString(self, output, debug = False): | |
117 | """ | |
118 | There are at least two variants of netcat, the OpenBSD version and the | |
119 | 'traditional' version. The verbose output differs between them, so we | |
120 | will try to cover both cases. | |
121 | """ | |
122 | print output | |
123 | nc_bsd_rx = re.compile(r'^Connection\s+to\s+(?P<host>\S+)\s+(?P<port>\d+)\s+port\s+\[(?P<protocol>tcp|udp)/(?P<service>[^\]]+)\]\s+succeeded.*') | |
124 | nc_sys_rx = re.compile(r'^(?P<host>\S+)\s+\[(?P<address>[0-9\.]+)\]\s+(?P<port>\d+)(?:\s+\((?P<service>[^)]+)\))?\s+open.*') | |
125 | ||
126 | nc_bsd_match = self.matchInOutput(nc_bsd_rx, output) | |
127 | if nc_bsd_match is not None: | |
128 | self.addEntry(nc_bsd_match.groupdict()) | |
129 | ||
130 | nc_sys_match = self.matchInOutput(nc_sys_rx, output) | |
131 | if nc_sys_match is not None: | |
132 | self.addEntry(nc_sys_match.groupdict()) | |
133 | ||
134 | return True | |
135 | ||
136 | def processCommandString(self, username, current_path, command_string): | |
137 | """ | |
138 | We need to use '-v' because otherwise netcat does not provide any | |
139 | output to indicate whether a connection has been successful; our | |
140 | regexp can certainly be improved, because we might get '-v' combined | |
141 | with other parameters, like in "nc -nv" | |
142 | """ | |
143 | if re.search(r'(nc|netcat)[^\d|\|]*-v', command_string) is None: | |
144 | return re.sub(r'(nc(?:\.traditional|\.openbsd)?|netcat)', r'\1 -v', command_string) | |
145 | ||
146 | return command_string | |
147 | ||
148 | def createPlugin(): | |
149 | return CmdNetcatPlugin() |
0 | couchdbkit==0.6.5 | |
1 | mockito==0.5.1 | |
2 | whoosh==2.5.5 | |
3 | argparse==1.1 | |
4 | IPy==0.75 | |
5 | restkit==4.2.2 | |
6 | requests==2.7.0 | |
7 | tornado==3.2 | |
8 | flask==0.10.1 | |
9 | colorama==0.3.2 | |
0 | couchdbkit | |
1 | mockito | |
2 | whoosh | |
3 | argparse | |
4 | IPy | |
5 | restkit | |
6 | requests | |
7 | tornado | |
8 | flask | |
9 | colorama | |
10 | twisted | |
11 | sqlalchemy | |
10 | 12 | #extra |
11 | psycopg2==2.5.4 | |
13 | psycopg2 |