1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# ***********************IMPORTANT NMAP LICENSE TERMS************************
5# *                                                                         *
6# * The Nmap Security Scanner is (C) 1996-2020 Insecure.Com LLC ("The Nmap  *
7# * Project"). Nmap is also a registered trademark of the Nmap Project.     *
8# *                                                                         *
9# * This program is distributed under the terms of the Nmap Public Source   *
10# * License (NPSL). The exact license text applying to a particular Nmap    *
11# * release or source code control revision is contained in the LICENSE     *
12# * file distributed with that version of Nmap or source code control       *
13# * revision. More Nmap copyright/legal information is available from       *
14# * https://nmap.org/book/man-legal.html, and further information on the    *
15# * NPSL license itself can be found at https://nmap.org/npsl. This header  *
16# * summarizes some key points from the Nmap license, but is no substitute  *
17# * for the actual license text.                                            *
18# *                                                                         *
19# * Nmap is generally free for end users to download and use themselves,    *
20# * including commercial use. It is available from https://nmap.org.        *
21# *                                                                         *
22# * The Nmap license generally prohibits companies from using and           *
23# * redistributing Nmap in commercial products, but we sell a special Nmap  *
24# * OEM Edition with a more permissive license and special features for     *
25# * this purpose. See https://nmap.org/oem                                  *
26# *                                                                         *
27# * If you have received a written Nmap license agreement or contract       *
28# * stating terms other than these (such as an Nmap OEM license), you may   *
29# * choose to use and redistribute Nmap under those terms instead.          *
30# *                                                                         *
31# * The official Nmap Windows builds include the Npcap software             *
32# * (https://npcap.org) for packet capture and transmission. It is under    *
33# * separate license terms which forbid redistribution without special      *
34# * permission. So the official Nmap Windows builds may not be              *
35# * redistributed without special permission (such as an Nmap OEM           *
36# * license).                                                               *
37# *                                                                         *
38# * Source is provided to this software because we believe users have a     *
39# * right to know exactly what a program is going to do before they run it. *
40# * This also allows you to audit the software for security holes.          *
41# *                                                                         *
42# * Source code also allows you to port Nmap to new platforms, fix bugs,    *
43# * and add new features.  You are highly encouraged to submit your         *
44# * changes as a Github PR or by email to the dev@nmap.org mailing list     *
45# * for possible incorporation into the main distribution. Unless you       *
46# * specify otherwise, it is understood that you are offering us very       *
47# * broad rights to use your submissions as described in the Nmap Public    *
48# * Source License Contributor Agreement. This is important because we      *
49# * fund the project by selling licenses with various terms, and also       *
50# * because the inability to relicense code has caused devastating          *
51# * problems for other Free Software projects (such as KDE and NASM).       *
52# *                                                                         *
53# * The free version of Nmap is distributed in the hope that it will be     *
54# * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of  *
55# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Warranties,        *
56# * indemnification and commercial support are all available through the    *
57# * Npcap OEM program--see https://nmap.org/oem.                            *
58# *                                                                         *
59# ***************************************************************************/
60
61import gtk
62
63from zenmapGUI.higwidgets.higwindows import HIGWindow
64from zenmapGUI.higwidgets.higboxes import HIGVBox, HIGHBox, HIGSpacer, \
65        hig_box_space_holder
66from zenmapGUI.higwidgets.higlabels import HIGSectionLabel, HIGEntryLabel
67from zenmapGUI.higwidgets.higscrollers import HIGScrolledWindow
68from zenmapGUI.higwidgets.higtextviewers import HIGTextView
69from zenmapGUI.higwidgets.higbuttons import HIGButton
70from zenmapGUI.higwidgets.higtables import HIGTable
71from zenmapGUI.higwidgets.higdialogs import HIGAlertDialog, HIGDialog
72from zenmapGUI.OptionBuilder import OptionBuilder
73from zenmapCore.Paths import Path
74from zenmapCore.UmitConf import CommandProfile
75from zenmapCore.UmitLogging import log
76import zenmapCore.I18N  # lgtm[py/unused-import]
77from zenmapCore.NmapOptions import NmapOptions
78
79
80class ProfileEditor(HIGWindow):
81    def __init__(self, command=None, profile_name=None,
82            deletable=True, overwrite=False):
83        HIGWindow.__init__(self)
84        self.connect("delete_event", self.exit)
85        self.set_title(_('Profile Editor'))
86        self.set_position(gtk.WIN_POS_CENTER)
87
88        self.deletable = deletable
89        self.profile_name = profile_name
90        self.overwrite = overwrite
91
92        # Used to block recursive updating of the command entry when the
93        # command entry causes the OptionBuilder widgets to change.
94        self.inhibit_command_update = False
95
96        self.__create_widgets()
97        self.__pack_widgets()
98
99        self.profile = CommandProfile()
100
101        self.ops = NmapOptions()
102        if profile_name:
103            log.debug("Showing profile %s" % profile_name)
104            prof = self.profile.get_profile(profile_name)
105
106            # Interface settings
107            self.profile_name_entry.set_text(profile_name)
108            self.profile_description_text.get_buffer().set_text(
109                    prof['description'])
110
111            command_string = prof['command']
112            self.ops.parse_string(command_string)
113        if command:
114            self.ops.parse_string(command)
115
116        self.option_builder = OptionBuilder(
117                Path.profile_editor, self.ops,
118                self.update_command, self.help_field.get_buffer())
119        log.debug("Option groups: %s" % str(self.option_builder.groups))
120        log.debug("Option section names: %s" % str(
121            self.option_builder.section_names))
122        #log.debug("Option tabs: %s" % str(self.option_builder.tabs))
123
124        for tab in self.option_builder.groups:
125            self.__create_tab(
126                    _(tab),
127                    _(self.option_builder.section_names[tab]),
128                    self.option_builder.tabs[tab])
129
130        self.update_command()
131
132    def command_entry_changed_cb(self, widget):
133        command_string = self.command_entry.get_text().decode("UTF-8")
134        self.ops.parse_string(command_string)
135        self.inhibit_command_update = True
136        self.option_builder.update()
137        self.inhibit_command_update = False
138
139    def update_command(self):
140        """Regenerate and display the command."""
141        if not self.inhibit_command_update:
142            # Block recursive updating of the OptionBuilder widgets when they
143            # cause a change in the command entry.
144            self.command_entry.handler_block(self.command_entry_changed_cb_id)
145            self.command_entry.set_text(self.ops.render_string())
146            self.command_entry.handler_unblock(
147                    self.command_entry_changed_cb_id)
148
149    def update_help_name(self, widget, extra):
150        self.help_field.get_buffer().set_text(
151                "Profile name\n\nThis is how the profile will be identified "
152                "in the drop-down combo box in the scan tab.")
153
154    def update_help_desc(self, widget, extra):
155        self.help_field.get_buffer().set_text(
156                "Description\n\nThe description is a full description of what "
157                "the scan does, which may be long.")
158
159    def __create_widgets(self):
160
161        ###
162        # Vertical box to keep 3 boxes
163        self.main_whole_box = HIGVBox()
164
165        self.upper_box = HIGHBox()
166        self.middle_box = HIGHBox()
167        self.lower_box = HIGHBox()
168
169        #self.main_vbox = HIGVBox()
170        self.command_entry = gtk.Entry()
171        self.command_entry_changed_cb_id = self.command_entry.connect(
172                "changed", self.command_entry_changed_cb)
173
174        self.scan_button = HIGButton(_("Scan"))
175        self.scan_button.connect("clicked", self.run_scan)
176
177        self.notebook = gtk.Notebook()
178
179        # Profile info page
180        self.profile_info_vbox = HIGVBox()
181        self.profile_info_label = HIGSectionLabel(_('Profile Information'))
182        self.profile_name_label = HIGEntryLabel(_('Profile name'))
183        self.profile_name_entry = gtk.Entry()
184        self.profile_name_entry.connect(
185                'enter-notify-event', self.update_help_name)
186        self.profile_description_label = HIGEntryLabel(_('Description'))
187        self.profile_description_scroll = HIGScrolledWindow()
188        self.profile_description_scroll.set_border_width(0)
189        self.profile_description_text = HIGTextView()
190        self.profile_description_text.connect(
191                'motion-notify-event', self.update_help_desc)
192
193        # Buttons
194        self.buttons_hbox = HIGHBox()
195
196        self.cancel_button = HIGButton(stock=gtk.STOCK_CANCEL)
197        self.cancel_button.connect('clicked', self.exit)
198
199        self.delete_button = HIGButton(stock=gtk.STOCK_DELETE)
200        self.delete_button.connect('clicked', self.delete_profile)
201
202        self.save_button = HIGButton(_("Save Changes"), stock=gtk.STOCK_SAVE)
203        self.save_button.connect('clicked', self.save_profile)
204
205        ###
206        self.help_vbox = HIGVBox()
207        self.help_label = HIGSectionLabel(_('Help'))
208        self.help_scroll = HIGScrolledWindow()
209        self.help_scroll.set_border_width(0)
210        self.help_field = HIGTextView()
211        self.help_field.set_cursor_visible(False)
212        self.help_field.set_left_margin(5)
213        self.help_field.set_editable(False)
214        self.help_vbox.set_size_request(200, -1)
215        ###
216
217    def __pack_widgets(self):
218
219        ###
220        self.add(self.main_whole_box)
221
222        # Packing command entry to upper box
223        self.upper_box._pack_expand_fill(self.command_entry)
224        self.upper_box._pack_noexpand_nofill(self.scan_button)
225
226        # Packing notebook (left) and help box (right) to middle box
227        self.middle_box._pack_expand_fill(self.notebook)
228        self.middle_box._pack_expand_fill(self.help_vbox)
229
230        # Packing buttons to lower box
231        self.lower_box.pack_end(self.buttons_hbox)
232
233        # Packing the three vertical boxes to the main box
234        self.main_whole_box._pack_noexpand_nofill(self.upper_box)
235        self.main_whole_box._pack_expand_fill(self.middle_box)
236        self.main_whole_box._pack_noexpand_nofill(self.lower_box)
237        ###
238
239        # Packing profile information tab on notebook
240        self.notebook.append_page(
241                self.profile_info_vbox, gtk.Label(_('Profile')))
242        self.profile_info_vbox.set_border_width(5)
243        table = HIGTable()
244        self.profile_info_vbox._pack_noexpand_nofill(self.profile_info_label)
245        self.profile_info_vbox._pack_expand_fill(HIGSpacer(table))
246
247        self.profile_description_scroll.add(self.profile_description_text)
248
249        vbox_desc = HIGVBox()
250        vbox_desc._pack_noexpand_nofill(self.profile_description_label)
251        vbox_desc._pack_expand_fill(hig_box_space_holder())
252
253        vbox_ann = HIGVBox()
254        vbox_ann._pack_expand_fill(hig_box_space_holder())
255
256        table.attach(
257                self.profile_name_label, 0, 1, 0, 1, xoptions=0, yoptions=0)
258        table.attach(self.profile_name_entry, 1, 2, 0, 1, yoptions=0)
259        table.attach(vbox_desc, 0, 1, 1, 2, xoptions=0)
260        table.attach(self.profile_description_scroll, 1, 2, 1, 2)
261
262        # Packing buttons on button_hbox
263        self.buttons_hbox._pack_expand_fill(hig_box_space_holder())
264        if self.deletable:
265            self.buttons_hbox._pack_noexpand_nofill(self.delete_button)
266        self.buttons_hbox._pack_noexpand_nofill(self.cancel_button)
267        self.buttons_hbox._pack_noexpand_nofill(self.save_button)
268
269        self.buttons_hbox.set_border_width(5)
270        self.buttons_hbox.set_spacing(6)
271
272        ###
273        self.help_vbox._pack_noexpand_nofill(self.help_label)
274        self.help_vbox._pack_expand_fill(self.help_scroll)
275        self.help_scroll.add(self.help_field)
276        self.help_vbox.set_border_width(1)
277        self.help_vbox.set_spacing(1)
278        ###
279
280    def __create_tab(self, tab_name, section_name, tab):
281        log.debug(">>> Tab name: %s" % tab_name)
282        log.debug(">>>Creating profile editor section: %s" % section_name)
283        vbox = HIGVBox()
284        if tab.notscripttab:  # if notscripttab is set
285            table = HIGTable()
286            table.set_row_spacings(2)
287            section = HIGSectionLabel(section_name)
288            vbox._pack_noexpand_nofill(section)
289            vbox._pack_noexpand_nofill(HIGSpacer(table))
290            vbox.set_border_width(5)
291            tab.fill_table(table, True)
292        else:
293            hbox = tab.get_hmain_box()
294            vbox.pack_start(hbox, True, True, 0)
295        self.notebook.append_page(vbox, gtk.Label(tab_name))
296
297    def save_profile(self, widget):
298        if self.overwrite:
299            self.profile.remove_profile(self.profile_name)
300        profile_name = self.profile_name_entry.get_text()
301        if profile_name == '':
302            alert = HIGAlertDialog(
303                    message_format=_('Unnamed profile'),
304                    secondary_text=_(
305                        'You must provide a name for this profile.'))
306            alert.run()
307            alert.destroy()
308
309            self.profile_name_entry.grab_focus()
310
311            return None
312
313        command = self.ops.render_string()
314
315        buf = self.profile_description_text.get_buffer()
316        description = buf.get_text(
317                buf.get_start_iter(), buf.get_end_iter())
318
319        try:
320            self.profile.add_profile(
321                    profile_name,
322                    command=command,
323                    description=description)
324        except ValueError:
325            alert = HIGAlertDialog(
326                    message_format=_('Disallowed profile name'),
327                    secondary_text=_('Sorry, the name "%s" is not allowed due '
328                        'to technical limitations. (The underlying '
329                        'ConfigParser used to store profiles does not allow '
330                        'it.) Choose a different name.' % profile_name))
331            alert.run()
332            alert.destroy()
333            return
334
335        self.scan_interface.toolbar.profile_entry.update()
336        self.destroy()
337
338    def clean_profile_info(self):
339        self.profile_name_entry.set_text('')
340        self.profile_description_text.get_buffer().set_text('')
341
342    def set_scan_interface(self, interface):
343        self.scan_interface = interface
344
345    def exit(self, *args):
346        self.destroy()
347
348    def delete_profile(self, widget=None, extra=None):
349        if self.deletable:
350            dialog = HIGDialog(buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
351                                        gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
352            alert = HIGEntryLabel('<b>' + _("Deleting Profile") + '</b>')
353            text = HIGEntryLabel(_(
354                'Your profile is going to be deleted! ClickOk to continue, '
355                'or Cancel to go back to Profile Editor.'))
356            hbox = HIGHBox()
357            hbox.set_border_width(5)
358            hbox.set_spacing(12)
359
360            vbox = HIGVBox()
361            vbox.set_border_width(5)
362            vbox.set_spacing(12)
363
364            image = gtk.Image()
365            image.set_from_stock(
366                    gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_DIALOG)
367
368            vbox.pack_start(alert)
369            vbox.pack_start(text)
370            hbox.pack_start(image)
371            hbox.pack_start(vbox)
372
373            dialog.vbox.pack_start(hbox)
374            dialog.vbox.show_all()
375
376            response = dialog.run()
377            dialog.destroy()
378            if response == gtk.RESPONSE_CANCEL:
379                return True
380            self.profile.remove_profile(self.profile_name)
381
382        self.update_profile_entry()
383        self.destroy()
384
385    def run_scan(self, widget=None):
386        command_string = self.command_entry.get_text().decode("UTF-8")
387        self.scan_interface.command_toolbar.command = command_string
388        self.scan_interface.start_scan_cb()
389        self.exit()
390
391    def update_profile_entry(self, widget=None, extra=None):
392        self.scan_interface.toolbar.profile_entry.update()
393        list = self.scan_interface.toolbar.profile_entry.get_model()
394        length = len(list)
395        if length > 0:
396            self.scan_interface.toolbar.profile_entry.set_active(0)
397
398
399if __name__ == '__main__':
400    p = ProfileEditor()
401    p.show_all()
402    gtk.main()
403