#!/usr/bin/python -O
"""
This is graphical frontend to msec.
"""
import os
import sys
import string
import getopt
import signal
import traceback
import Queue
from textwrap import wrap
# PyGTK
import warnings
warnings.filterwarnings('error', module='gtk')
try:
import gtk
import pygtk
import gobject
import pango
except Warning, e:
print "ERROR: %s" % e
print "Exiting.."
sys.exit(1)
warnings.resetwarnings()
# config
import config
# version
try:
from version import version
except:
version = "development version"
# libmsec
from libmsec import MSEC, PERMS, Log
import logging
# localization
import gettext
try:
gettext.install("msec")
except IOError:
_ = str
# text strings
LEVEL_SECURITY_TEXT=_("""Choose security level
This application allows you to configure your system security. If you wish
to activate it, choose the appropriate security level: """)
STANDARD_LEVEL_DESCRIPTION="\n".join(wrap(_("""This profile configures a reasonably safe set of security features. It activates several non-intrusive periodic system checks. This is the suggested level for Desktop."""), 80))
SECURE_LEVEL_DESCRIPTION="\n".join(wrap(_("""This profile is configured to provide maximum security, even at the cost of limiting the remote access to the system. It also runs a wider set of periodic checks. This level is suggested for Servers and security-concerned systems . """), 80))
SYSTEM_SECURITY_TEXT=_("""System security options
These options control the local security configuration, such as the login restrictions,
password configurations, integration with other security tools, and default file creation
permissions. """)
NETWORK_SECURITY_TEXT=_("""Network security options
These options define the network security against remote threats, unauthorized accesses,
and breakin attempts. """)
PERIODIC_SECURITY_TEXT=_("""Periodic security checks
These options configure the security checks that should be executed periodically. """)
EXCEPTIONS_TEXT=_("""Exceptions
Here you can configure the allowed exceptions for msec periodic security
checks. For each supported test, you may add as many exceptions as you want
for each check. Note that each exception is parsed as a regexp.""")
PERMISSIONS_SECURITY_TEXT=_("""File permissions
These options allow to fine-tune system permissions for important files and directories.
The following permissions are checked periodically, and any change to the owner, group,
or current permission is reported. The permissions can be enforced, automatically
changing them to the specified values when a change is detected. """)
SAVE_SETTINGS_TEXT=_("""Save and apply new configuration?""")
# gui-related settings
DEFAULT_SPACING=5
BANNER="msec.png"
class MsecGui:
"""Msec GUI"""
# common columns
(COLUMN_OPTION, COLUMN_DESCR, COLUMN_VALUE, COLUMN_CUSTOM) = range(4)
(COLUMN_PATH, COLUMN_USER, COLUMN_GROUP, COLUMN_PERM, COLUMN_FORCE) = range(5)
(COLUMN_EXCEPTION, COLUMN_EXCEPTION_VALUE, COLUMN_POS) = range(3)
def __init__(self, log, msec, perms, msecconfig, permconfig, exceptions, embed=None):
"""Initializes gui"""
self.log = log
self.msec = msec
self.perms = perms
# current configuration
self.msecconfig = msecconfig
self.permconfig = permconfig
self.exceptions = exceptions
# pre-defined standard configurations
self.msec_defaults = {
config.NONE_LEVEL: config.load_defaults(log, config.NONE_LEVEL),
config.STANDARD_LEVEL: config.load_defaults(log, config.STANDARD_LEVEL),
config.SECURE_LEVEL: config.load_defaults(log, config.SECURE_LEVEL),
}
# pre-defined permissions
self.perm_defaults = {
config.NONE_LEVEL: config.load_default_perms(log, config.NONE_LEVEL),
config.STANDARD_LEVEL: config.load_default_perms(log, config.STANDARD_LEVEL),
config.SECURE_LEVEL: config.load_default_perms(log, config.SECURE_LEVEL),
}
# pre-load documentation for all possible options
self.descriptions = {}
for option in config.SETTINGS:
# get documentation for item
config.find_doc(msec, option, cached=self.descriptions)
# loading the current config
self.reload_config()
if embed:
# embedding in MCC
self.window = gtk.Plug(embed)
else:
# running standalone
self.window = gtk.Window()
self.window.set_default_size(640, 440)
self.window.connect('delete-event', self.quit)
# are we enforcing a level
self.enforced_level = None
self.enforcing_level = False
main_vbox = gtk.VBox(homogeneous=False, spacing=5)
self.window.add(main_vbox)
# menu
menubar = gtk.MenuBar()
main_vbox.pack_start(menubar, False, False)
menus = [
(_("_File"),
[
(_("_Save configuration"), self.ok),
# (None, None),
# (_("_Import configuration"), None),
# (_("_Export configuration"), None),
# (None, None),
(_("_Quit"), self.quit),
]),
(_("_Help"),
[
(_("_Help"), None),
(_("_About"), None),
]),
]
# building menus
for topmenu, items in menus:
# file menu
filemenu = gtk.MenuItem(topmenu)
menubar.add(filemenu)
menu = gtk.Menu()
filemenu.set_submenu(menu)
group = None
for submenu, callback in items:
menuitem = gtk.MenuItem(submenu)
if callback:
menuitem.connect('activate', callback)
else:
menuitem.set_sensitive(False)
menu.add(menuitem)
# creating logo if running inside mcc
if embed:
banner = gtk.HBox(homogeneous=False, spacing=10)
try:
# logo
image = gtk.Image()
pixbuf = gtk.gdk.pixbuf_new_from_file("%s/%s" % (config.MSEC_DIR, BANNER))
image.set_from_pixbuf(pixbuf)
banner.pack_start(image, False, False)
label = gtk.Label(_("MSEC: System Security and Audit"))
label.modify_font(pango.FontDescription("13"))
banner.pack_start(label, False, False)
main_vbox.pack_start(banner, False, False)
except:
print "Banner %s Not found" % ("%s/%s" % (config.MSEC_DIR, BANNER))
# toolbar
toolbar = gtk.Toolbar()
toolbar.set_style(gtk.TOOLBAR_ICONS)
toolbar.set_tooltips(True)
toolbar_item = gtk.ToolButton("Save")
toolbar_item.set_stock_id(gtk.STOCK_SAVE)
toolbar_item.connect("clicked", self.ok)
toolbar_item.set_tooltip_text(_("Save and apply current policy"))
toolbar.insert(toolbar_item, -1)
toolbar_item = gtk.ToolButton("Quit")
toolbar_item.set_stock_id(gtk.STOCK_QUIT)
toolbar_item.connect("clicked", self.quit)
toolbar_item.set_tooltip_text(_("Quit"))
toolbar.insert(toolbar_item, -1)
main_vbox.pack_start(toolbar, False, False)
# creating tabs
self.notebook = gtk.Notebook()
main_vbox.pack_start(self.notebook)
# data to change the values
self.current_options_view = {}
# checkboxes callbacks
self.checkboxes_callbacks = {}
# tabs to create in the intrerface
tabs = [
(1, self.level_security_page, _("Basic security")),
(2, self.system_security_page, _("System security")),
(3, self.network_security_page, _("Network security")),
(4, self.periodic_security_page, _("Periodic checks")),
(5, self.exceptions_page, _("Exceptions")),
(6, self.permissions_security_page, _("Permissions")),
]
for id, callback, label in tabs:
self.notebook.append_page(callback(id), gtk.Label(label))
# are we enabled?
self.toggle_level(self.base_level)
# pending signals
self.signals = Queue.Queue()
gobject.timeout_add(500, self.check_signals)
self.window.show_all()
def check_signals(self):
"""Checks for received signals"""
if not self.signals.empty():
s = self.signals.get()
if s == signal.SIGTERM:
self.quit(self.window)
gobject.timeout_add(500, self.check_signals)
def check_for_changes(self, curconfig, curperms):
"""Checks for changes in configuration. Returns number of configuration
changes, the description of changes, and results of msec dry run"""
# apply config and preview changes
self.log.start_buffer()
self.msec.apply(curconfig)
self.msec.commit(False)
messages = self.log.get_buffer()
# check for changed options
num_changes = 0
changes = []
for name, type, oldconf, curconf in [ (_("MSEC option changes"), _("option"), self.oldconfig, curconfig),
(_("System permissions changes"), _("permission check"), self.oldperms, curperms),
]:
# check for changes
opt_changes = []
opt_adds = []
opt_dels = []
# changed options
curchanges = ""
opt_changes = [opt for opt in oldconf if (curconf.get(opt) != oldconf.get(opt) and curconf.get(opt) != None and curconf.get(opt) != None)]
if len(opt_changes) > 0:
curchanges += "\n\t" + "\n\t".join([_("changed %s %s (%s -> %s)") % (type, param, oldconf.get(param), curconf.get(param)) for param in opt_changes])
num_changes += len(opt_changes)
# new options
opt_adds = [opt for opt in curconf.list_options() if (opt not in oldconf and curconf.get(opt))]
if len(opt_adds) > 0:
curchanges += "\n\t" + "\n\t".join([_("added %s %s (%s)") % (type, param, curconf.get(param)) for param in opt_adds])
num_changes += len(opt_adds)
# removed options
opt_dels = [opt for opt in oldconf if ((opt not in curconf.list_options() or curconf.get(opt) == None) and oldconf.get(opt))]
if len(opt_dels) > 0:
curchanges += "\n\t" + "\n\t".join([_("removed %s %s") % (type, param) for param in opt_dels])
num_changes += len(opt_dels)
# adding labels
if curchanges == "":
curchanges = _("no changes")
# store the current changes
changes.append((name, curchanges))
# return what we found
return num_changes, changes, messages
def ok(self, widget):
"""Ok button"""
curconfig = self.msecconfig
curperms = self.permconfig
# creating preview window
dialog = gtk.Dialog(_("Saving changes.."),
self.window, gtk.DIALOG_MODAL,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK)
)
dialog.set_default_size(640, 300)
dialog.set_default_response(gtk.RESPONSE_OK)
label = gtk.Label(SAVE_SETTINGS_TEXT)
dialog.vbox.set_spacing(DEFAULT_SPACING)
dialog.vbox.pack_start(label, False, False, padding=DEFAULT_SPACING)
dialog.set_resizable(False)
# detailed information
exp_vbox = gtk.VBox()
# scrolledwindow
sw = gtk.ScrolledWindow()
sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
exp_vbox.pack_start(sw, padding=DEFAULT_SPACING)
vbox = gtk.VBox()
exp_vbox.set_size_request(640, 280)
sw.add_with_viewport(vbox)
# were there changes in configuration?
num_changes, allchanges, messages = self.check_for_changes(curconfig, curperms)
# TODO: FIX
for name, changes in allchanges:
label = gtk.Label(_('%s: %s\n') % (name, changes))
label.set_use_markup(True)
label.set_property("xalign", 0.0)
vbox.pack_start(label, False, False, padding=DEFAULT_SPACING)
# see if there were any changes to system files
for msg in messages['info']:
if msg.find(config.MODIFICATIONS_FOUND) != -1 or msg.find(config.MODIFICATIONS_NOT_FOUND) != -1:
label = gtk.Label(_("MSEC test run results: %s") % msg)
label.set_line_wrap(True)
label.set_use_markup(True)
label.set_property("xalign", 0.0)
vbox.pack_start(label, False, False, padding=DEFAULT_SPACING)
break
# adding specific messages
advanced = gtk.Expander(_("Details"))
vbox_advanced = gtk.VBox()
advanced.add(vbox_advanced)
vbox.pack_start(advanced, False, False, padding=DEFAULT_SPACING)
for cat in ['info', 'critical', 'error', 'warn', 'debug']:
msgs = messages[cat]
expander = gtk.Expander(_('MSEC messages (%s): %d') % (cat, len(msgs)))
textview = gtk.TextView()
textview.set_wrap_mode(gtk.WRAP_WORD_CHAR)
textview.set_editable(False)
expander.add(textview)
count = 1
for msg in msgs:
buffer = textview.get_buffer()
end = buffer.get_end_iter()
buffer.insert(end, "%d: %s\n" % (count, msg))
count += 1
vbox_advanced.pack_start(expander, False, False, padding=DEFAULT_SPACING)
# hide all information in an expander
expander = gtk.Expander(_("Details (%d changes)..") % num_changes)
expander.add(exp_vbox)
dialog.vbox.pack_start(expander, False, False, padding=DEFAULT_SPACING)
dialog.show_all()
response = dialog.run()
if response != gtk.RESPONSE_OK:
dialog.destroy()
return False
dialog.destroy()
# new base level
self.msecconfig.set("BASE_LEVEL", self.base_level)
# saving the configuration
self.msecconfig.save()
self.msec.apply(self.msecconfig)
self.msec.commit(True)
# saving permissions
self.permconfig.save()
self.reload_config()
return True
def reload_config(self):
"""Reloads config files"""
# msecconfig
self.msecconfig.reset()
self.msecconfig.load()
# permconfig
self.permconfig.reset()
self.permconfig.load()
# exceptions
self.exceptions.reset()
self.exceptions.load()
# saving old config
self.oldconfig = {}
for opt in self.msecconfig.list_options():
self.oldconfig[opt] = self.msecconfig.get(opt)
# permissions
self.oldperms = {}
for opt in self.permconfig.list_options():
self.oldperms[opt] = self.permconfig.get(opt)
# what level are we?
level = self.msecconfig.get("BASE_LEVEL")
if not level:
self.log.info(_("No base msec level specified, using '%s'") % config.STANDARD_LEVEL)
self.base_level = config.STANDARD_LEVEL
elif level == config.NONE_LEVEL or level == config.STANDARD_LEVEL or level == config.SECURE_LEVEL:
self.log.info(_("Detected base msec level '%s'") % level)
self.base_level = level
else:
# custom level?
# TODO: notify user about this
self.log.info(_("Custom base config level '%s' found. Will default to '%s'") % (level, config.STANDARD_LEVEL))
self.base_level = config.STANDARD_LEVEL
def create_treeview(self, options):
"""Creates a treeview from given list of options"""
sw = gtk.ScrolledWindow()
sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
# list of options
lstore = gtk.ListStore(
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_INT)
# treeview
treeview = gtk.TreeView(lstore)
treeview.set_rules_hint(True)
treeview.set_search_column(self.COLUMN_DESCR)
treeview.connect('row-activated', self.option_changed, lstore)
# configuring columns
# column for option names
renderer = gtk.CellRendererText()
renderer.set_property('width', 200)
column = gtk.TreeViewColumn(_('Security Option'), renderer, text=self.COLUMN_OPTION, weight=self.COLUMN_CUSTOM)
column.set_sort_column_id(self.COLUMN_OPTION)
column.set_resizable(True)
column.set_expand(True)
treeview.append_column(column)
# column for descriptions
renderer = gtk.CellRendererText()
renderer.set_property('wrap-width', 400)
renderer.set_property('wrap-mode', pango.WRAP_WORD_CHAR)
column = treeview.insert_column_with_attributes(-1, _('Description'), renderer, text=self.COLUMN_DESCR, weight=self.COLUMN_CUSTOM)
column.set_expand(True)
#treeview.append_column(column)
# column for values
column = gtk.TreeViewColumn(_('Value'), gtk.CellRendererText(), text=self.COLUMN_VALUE, weight=self.COLUMN_CUSTOM)
column.set_sort_column_id(self.COLUMN_VALUE)
treeview.append_column(column)
sw.add(treeview)
for option in options:
# retreiving option description
if not config.SETTINGS.has_key(option):
# invalid option
self.log.error(_("Invalid option '%s'!") % option)
continue
# getting level settings, callback and valid params
callback, params = config.SETTINGS[option]
# now for the value
value = self.msecconfig.get(option)
# check for disabled options
if not value:
value = config.OPTION_DISABLED
# description
doc = config.find_doc(self.msec, option, self.descriptions)
# was it changed? if yes, change description to italic
if self.option_is_changed(option, value):
custom = pango.WEIGHT_BOLD
else:
custom = pango.WEIGHT_NORMAL
# building the option
iter = lstore.append()
lstore.set(iter,
self.COLUMN_OPTION, option,
self.COLUMN_DESCR, doc,
self.COLUMN_VALUE, value,
self.COLUMN_CUSTOM, custom,
)
return sw, lstore
def option_is_changed(self, option, value, level=None):
"""Checks if the option is different from one specified by base level"""
if not level:
level = self.base_level
conf = self.msec_defaults[level]
if conf.get(option) != value:
# it was changed
return True
else:
return False
def level_security_page(self, id):
"""Builds the basic security page"""
vbox = gtk.VBox(homogeneous=False)
entry = gtk.Label(LEVEL_SECURITY_TEXT)
entry.set_use_markup(True)
vbox.pack_start(entry, False, False)
# none
self.msec_enabled = gtk.CheckButton(label=_("Enable MSEC tool"))
if self.base_level != config.NONE_LEVEL:
self.msec_enabled.set_active(True)
self.msec_enabled.connect('clicked', self.enable_disable_msec)
vbox.pack_start(self.msec_enabled, False, False)
# security levels
self.levels_frame = gtk.Frame(_("Select the base security level"))
levels_vbox = gtk.VBox(homogeneous=False)
self.levels_frame.add(levels_vbox)
# default
self.button_default = gtk.RadioButton(group=None, label=_("Standard"))
self.button_default.connect('clicked', self.force_level, config.STANDARD_LEVEL)
if self.base_level == config.STANDARD_LEVEL:
self.button_default.set_active(True)
levels_vbox.pack_start(self.button_default, False, False)
# default level description
label = gtk.Label(STANDARD_LEVEL_DESCRIPTION)
levels_vbox.pack_start(label, False, False)
# secure
self.button_secure = gtk.RadioButton(group=self.button_default, label=_("Secure"))
self.button_secure.connect('clicked', self.force_level, config.SECURE_LEVEL)
if self.base_level == config.SECURE_LEVEL:
self.button_secure.set_active(True)
levels_vbox.pack_start(self.button_secure, False, False)
# secure level description
label = gtk.Label(SECURE_LEVEL_DESCRIPTION)
levels_vbox.pack_start(label, False, False)
# putting levels to vbox
vbox.pack_start(self.levels_frame, False, False)
# notifications by email
self.notify_mail = gtk.CheckButton(_("Send security alerts by email"))
if self.msecconfig.get("MAIL_WARN") == "yes":
self.notify_mail.set_active(True)
vbox.pack_start(self.notify_mail, False, False)
# email address
hbox = gtk.HBox()
label = gtk.Label(_("System administrator email address:"))
label.set_property("xalign", 0.2)
hbox.pack_start(label, False, False)
self.email_entry = gtk.Entry()
email = self.msecconfig.get("MAIL_USER")
if not email:
email = ""
self.email_entry.set_text(email)
self.email_entry.connect('changed', self.change_email)
hbox.pack_start(self.email_entry, False, False)
vbox.pack_start(hbox, False, False)
# updating the mail address/checkbox relationship
self.notify_mail.connect('clicked', self.notify_mail_changed, hbox)
self.checkboxes_callbacks["MAIL_WARN"] = (self.notify_mail_changed, self.notify_mail, hbox)
self.notify_mail_changed(self.notify_mail, hbox)
# notifications on desktop
self.notify_desktop = gtk.CheckButton(_("Display security alerts on desktop"))
if self.msecconfig.get("NOTIFY_WARN") == "yes":
self.notify_desktop.set_active(True)
self.notify_desktop.connect('clicked', self.notify_changed, None)
vbox.pack_start(self.notify_desktop, False, False)
self.checkboxes_callbacks["NOTIFY_WARN"] = (self.notify_changed, self.notify_desktop, None)
return vbox
def change_email(self, widget):
"""Email address was changed"""
email = widget.get_text()
self.msecconfig.set("MAIL_USER", email)
def notify_mail_changed(self, widget, param):
"""Changes to mail notifications"""
status = widget.get_active()
if status:
self.msecconfig.set("MAIL_WARN", "yes")
param.set_sensitive(True)
else:
self.msecconfig.set("MAIL_WARN", "no")
param.set_sensitive(False)
def notify_changed(self, widget, param):
"""Changes to mail notifications"""
status = widget.get_active()
if status:
self.msecconfig.set("NOTIFY_WARN", "yes")
else:
self.msecconfig.set("NOTIFY_WARN", "no")
def enable_disable_msec(self, widget):
"""Enables/disables msec tool"""
newstatus = widget.get_active()
if newstatus == False:
self.toggle_level(config.NONE_LEVEL, force=True)
else:
# what level are we toggling?
if self.button_default.get_active():
level = config.STANDARD_LEVEL
else:
level = config.SECURE_LEVEL
self.toggle_level(level, force=True)
def toggle_level(self, level, force=False):
"""Enables/disables graphical items for msec"""
if level != config.NONE_LEVEL:
enabled = True
else:
enabled = False
# update notebook pages
npages = self.notebook.get_n_pages()
self.levels_frame.set_sensitive(enabled)
for page in range(1, npages):
curpage = self.notebook.get_nth_page(page)
curpage.set_sensitive(enabled)
label = self.notebook.get_tab_label(curpage)
label.set_sensitive(enabled)
if level == self.base_level:
# Not changing anything
return
# what is the current level?
defconfig = self.msec_defaults[level]
for z in self.current_options_view:
options, curconfig = self.current_options_view[z]
iter = options.get_iter_root()
# what options are we changing
if curconfig.__class__ == config.MsecConfig:
# main msec options
while iter:
option = options.get_value(iter, self.COLUMN_OPTION)
curvalue = options.get_value(iter, self.COLUMN_VALUE)
newvalue = defconfig.get(option)
# changing option
if force:
if curvalue != newvalue:
self.log.debug("%s: %s -> %s" % (option, curvalue, newvalue))
options.set(iter, self.COLUMN_VALUE, newvalue)
# reset customization
curconfig.set(option, newvalue)
# set option as normal
options.set(iter, self.COLUMN_CUSTOM, pango.WEIGHT_NORMAL)
## skip custom options
#print "Base level: %s" % self.base_level
#if self.option_is_changed(option, curvalue):
# # custom option
# print "Custom option detected: %s" % option
iter = options.iter_next(iter)
elif curconfig.__class__ == config.PermConfig:
# Use should enforce it in the Permission tab
pass
else:
#print curconfig.__class__
pass
# checkboxes
for option in self.checkboxes_callbacks:
if force:
func, widget, callback = self.checkboxes_callbacks[option]
if defconfig.get(option) == "yes":
widget.set_active(True)
else:
widget.set_active(False)
self.msecconfig.set(option, defconfig.get(option))
func(widget, callback)
# finally, change new base_level
self.base_level = level
def force_level(self, widget, level):
"""Defines a given security level"""
if widget.get_active():
#self.base_level = level
# update everything
self.toggle_level(level, force=True)
def system_security_page(self, id):
"""Builds the network security page"""
vbox = gtk.VBox(homogeneous=False)
entry = gtk.Label(SYSTEM_SECURITY_TEXT)
entry.set_use_markup(True)
vbox.pack_start(entry, False, False)
# system security options
options_view, model = self.create_treeview(config.SETTINGS_SYSTEM)
self.current_options_view[id] = (model, self.msecconfig)
vbox.pack_start(options_view)
return vbox
def network_security_page(self, id):
"""Builds the network security page"""
vbox = gtk.VBox(homogeneous=False)
entry = gtk.Label(NETWORK_SECURITY_TEXT)
entry.set_use_markup(True)
vbox.pack_start(entry, False, False)
# network security options
options_view, model = self.create_treeview(config.SETTINGS_NETWORK)
self.current_options_view[id] = (model, self.msecconfig)
vbox.pack_start(options_view)
return vbox
def periodic_security_page(self, id):
"""Builds the network security page"""
vbox = gtk.VBox(homogeneous=False)
entry = gtk.Label(PERIODIC_SECURITY_TEXT)
entry.set_use_markup(True)
vbox.pack_start(entry, False, False)
periodic_checks = self.msecconfig.get("CHECK_SECURITY")
self.periodic_checks = gtk.CheckButton(_("Enable periodic security checks"))
if periodic_checks == "yes":
self.periodic_checks.set_active(True)
vbox.pack_start(self.periodic_checks, False, False)
# network security options
options_view, model = self.create_treeview(config.SETTINGS_PERIODIC)
vbox.pack_start(options_view)
# see if these tests are enabled
self.periodic_checks.connect('clicked', self.periodic_tests, options_view)
if periodic_checks == 'no':
# disable all periodic tests
options_view.set_sensitive(False)
# save options
self.current_options_view[id] = (model, self.msecconfig)
# save the checkboxes
self.checkboxes_callbacks["CHECK_SECURITY"] = (self.periodic_tests, self.periodic_checks, options_view)
return vbox
def periodic_tests(self, widget, options):
'''Enables/disables periodic security tests.'''
status = widget.get_active()
if status:
self.msecconfig.set("CHECK_SECURITY", "yes")
options.set_sensitive(True)
else:
self.msecconfig.set("CHECK_SECURITY", "no")
options.set_sensitive(False)
def exceptions_page(self, id):
"""Builds the exceptions page"""
vbox = gtk.VBox(homogeneous=False)
entry = gtk.Label(EXCEPTIONS_TEXT)
entry.set_use_markup(True)
vbox.pack_start(entry, False, False)
sw = gtk.ScrolledWindow()
sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
# list of options
lstore = gtk.ListStore(
gobject.TYPE_STRING,
gobject.TYPE_STRING
)
# treeview
treeview = gtk.TreeView(lstore)
treeview.set_rules_hint(True)
treeview.set_search_column(self.COLUMN_EXCEPTION)
# TODO: fix
treeview.connect('row-activated', self.exception_changed, lstore)
# configuring columns
# column for exception position
column = gtk.TreeViewColumn(_('Security check'), gtk.CellRendererText(), text=self.COLUMN_EXCEPTION)
column.set_sort_column_id(self.COLUMN_EXCEPTION)
column.set_expand(True)
treeview.append_column(column)
# column for check exception
column = gtk.TreeViewColumn(_('Exception'), gtk.CellRendererText(), text=self.COLUMN_EXCEPTION_VALUE)
column.set_sort_column_id(self.COLUMN_EXCEPTION_VALUE)
column.set_expand(True)
treeview.append_column(column)
sw.add(treeview)
for option, value in self.exceptions.list_options():
# building the option
iter = lstore.append()
lstore.set(iter,
self.COLUMN_EXCEPTION, option,
self.COLUMN_EXCEPTION_VALUE, value,
)
vbox.pack_start(sw)
self.current_options_view[id] = (lstore, self.exceptions)
# buttons hbox
hbox = gtk.HBox(homogeneous=True, spacing=10)
# add
button = gtk.Button(_("Add a rule"))
button.connect('clicked', self.add_exception, lstore)
hbox.pack_start(button, False)
# delete
button = gtk.Button(_("Delete"))
button.connect('clicked', self.remove_exception, treeview)
hbox.pack_start(button, False)
vbox.pack_start(hbox, False, False)
return vbox
def permissions_security_page(self, id):
"""Builds the network security page"""
vbox = gtk.VBox(homogeneous=False)
entry = gtk.Label(PERMISSIONS_SECURITY_TEXT)
entry.set_use_markup(True)
vbox.pack_start(entry, False, False)
sw = gtk.ScrolledWindow()
sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
# list of options
lstore = gtk.ListStore(
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_STRING,
gobject.TYPE_BOOLEAN)
# treeview
treeview = gtk.TreeView(lstore)
treeview.set_rules_hint(True)
treeview.set_search_column(self.COLUMN_DESCR)
# TODO: fix
treeview.connect('row-activated', self.permission_changed, lstore)
# configuring columns
# column for path mask
column = gtk.TreeViewColumn(_('Path'), gtk.CellRendererText(), text=self.COLUMN_PATH)
column.set_sort_column_id(self.COLUMN_PATH)
column.set_expand(True)
treeview.append_column(column)
# column for user
column = gtk.TreeViewColumn(_('User'), gtk.CellRendererText(), text=self.COLUMN_USER)
column.set_sort_column_id(self.COLUMN_USER)
column.set_expand(True)
treeview.append_column(column)
# column for group
column = gtk.TreeViewColumn(_('Group'), gtk.CellRendererText(), text=self.COLUMN_GROUP)
column.set_sort_column_id(self.COLUMN_GROUP)
column.set_expand(True)
treeview.append_column(column)
# column for permissions
column = gtk.TreeViewColumn(_('Permissions'), gtk.CellRendererText(), text=self.COLUMN_PERM)
column.set_sort_column_id(self.COLUMN_VALUE)
column.set_expand(True)
treeview.append_column(column)
# column for force option
renderer = gtk.CellRendererToggle()
renderer.connect('toggled', self.toggle_enforced, lstore)
column = gtk.TreeViewColumn(_('Enforce'), renderer, active=self.COLUMN_FORCE)
column.set_sort_column_id(self.COLUMN_FORCE)
column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
column.set_fixed_width(50)
column.set_expand(True)
treeview.append_column(column)
sw.add(treeview)
for file in self.permconfig.list_options():
user_s, group_s, perm_s, force = self.permconfig.get(file)
# convert to boolean
if force:
force = True
else:
force = False
# building the option
iter = lstore.append()
lstore.set(iter,
self.COLUMN_PATH, file,
self.COLUMN_USER, user_s,
self.COLUMN_GROUP, group_s,
self.COLUMN_PERM, perm_s,
self.COLUMN_FORCE, force,
)
vbox.pack_start(sw)
self.current_options_view[id] = (lstore, self.permconfig)
# buttons hbox
hbox = gtk.HBox(homogeneous=True, spacing=10)
# # up
# button = gtk.Button(_("Up"))
# button.connect('clicked', self.move_rule_up, lstore)
# hbox.pack_start(button, False)
# # down
# button = gtk.Button(_("Down"))
# button.connect('clicked', self.move_rule_up, lstore)
# hbox.pack_start(button, False)
# default
button = gtk.Button(_("Reset to default level permissions"))
button.connect('clicked', self.reset_permissions, lstore)
hbox.pack_start(button, False)
# add
button = gtk.Button(_("Add a rule"))
button.connect('clicked', self.add_permission_check, lstore)
hbox.pack_start(button, False)
# delete
button = gtk.Button(_("Delete"))
button.connect('clicked', self.remove_permission_check, treeview)
hbox.pack_start(button, False)
## edit
#button = gtk.Button(_("Edit"))
#button.connect('clicked', self.move_rule_up, lstore)
#hbox.pack_start(button, False)
vbox.pack_start(hbox, False, False)
return vbox
def reset_permissions(self, widget, model):
"""Reset permissions to default specified by level"""
model.clear()
self.permconfig.reset()
defperms = self.perm_defaults[self.base_level]
for file in defperms.list_options():
user_s, group_s, perm_s, force_s = defperms.get(file)
# convert to boolean
if force_s:
force_val = True
else:
force_val = False
# building the option
iter = model.append()
model.set(iter,
self.COLUMN_PATH, file,
self.COLUMN_USER, user_s,
self.COLUMN_GROUP, group_s,
self.COLUMN_PERM, perm_s,
self.COLUMN_FORCE, force_val,
)
# changing back force value
self.permconfig.set(file, (user_s, group_s, perm_s, force_s))
def remove_exception(self, widget, treeview):
"""Removes an exception from list"""
model, iter = treeview.get_selection().get_selected()
if not iter:
# nothing selected
return
pos, = model.get_path(iter)
self.exceptions.remove(pos)
model.remove(iter)
# save exceptions
self.exceptions.save()
def remove_permission_check(self, widget, treeview):
"""Removes a permission check for file"""
model, iter = treeview.get_selection().get_selected()
if not iter:
# nothing selected
return
file = model.get_value(iter, self.COLUMN_PATH)
self.permconfig.remove(file)
model.remove(iter)
def toggle_enforced(self, cell, path, model):
'''Toggles a forced permission on an item'''
iter = model.get_iter((int(path),))
file = model.get_value(iter, self.COLUMN_PATH)
fixed = model.get_value(iter, self.COLUMN_FORCE)
user, group, perm, force = self.permconfig.get(file)
# do something with the value
fixed = not fixed
# set new value
model.set(iter, self.COLUMN_FORCE, fixed)
if fixed:
force = "force"
else:
force = ""
self.permconfig.set(file, (user, group, perm, force))
def add_permission_check(self, widget, model):
"""Adds a permission check"""
return self.permission_changed(None, None, None, model)
def add_exception(self, widget, model):
"""Adds a new exception"""
return self.exception_changed(None, None, None, model)
def exception_changed(self, treeview, path, col, model):
"""Processes an exception change. If path is None, adds a new item."""
if path:
iter = model.get_iter(path)
exception_pos, = path
module = model.get_value(iter, self.COLUMN_EXCEPTION)
exception = model.get_value(iter, self.COLUMN_EXCEPTION_VALUE)
title = _("Editing exception")
else:
exception_pos = -1
module = ""
exception = ""
title = _("Adding new exception")
# asks for new parameter value
dialog = gtk.Dialog(title,
self.window, 0,
(gtk.STOCK_OK, gtk.RESPONSE_OK,
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
label = gtk.Label(_("Editing exception. Please select the correspondent msec check and exception value\n"))
label.set_line_wrap(True)
label.set_use_markup(True)
dialog.vbox.pack_start(label, False, False)
# module
hbox = gtk.HBox()
hbox.pack_start(gtk.Label(_("Check: ")))
entry_module = gtk.combo_box_new_text()
pos = 0
for item in config.CHECKS_WITH_EXCEPTIONS:
entry_module.append_text(item)
if item == module:
entry_module.set_active(pos)
pos += 1
if not module:
entry_module.set_active(0)
hbox.pack_start(entry_module)
dialog.vbox.pack_start(hbox, False, False)
# exception
hbox = gtk.HBox()
hbox.pack_start(gtk.Label(_("Exception: ")))
entry_exception = gtk.Entry()
entry_exception.set_text(exception)
hbox.pack_start(entry_exception)
dialog.vbox.pack_start(hbox, False, False)
dialog.show_all()
response = dialog.run()
if response != gtk.RESPONSE_OK:
dialog.destroy()
return
new_check = entry_module.get_active_text()
new_exception = entry_exception.get_text()
dialog.destroy()
self.exceptions.set(exception_pos, (new_check, new_exception))
if not path:
# adding new entry
iter = model.append()
model.set(iter, self.COLUMN_EXCEPTION, new_check)
model.set(iter, self.COLUMN_EXCEPTION_VALUE, new_exception)
# save exceptions
self.exceptions.save()
def permission_changed(self, treeview, path, col, model):
"""Processes a permission change. If path is None, adds a new item."""
if path:
iter = model.get_iter(path)
file = model.get_value(iter, self.COLUMN_PATH)
user = model.get_value(iter, self.COLUMN_USER)
group = model.get_value(iter, self.COLUMN_GROUP)
perm = model.get_value(iter, self.COLUMN_PERM)
force = model.get_value(iter, self.COLUMN_FORCE)
title = _("Changing permissions for %s") % file
else:
file = ""
user = ""
group = ""
perm = ""
force = ""
title = _("Adding new permission check")
if not force:
force = ""
else:
force = "force"
# asks for new parameter value
dialog = gtk.Dialog(title,
self.window, 0,
(gtk.STOCK_OK, gtk.RESPONSE_OK,
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
label = gtk.Label(_("Changing permissions on %s\nPlease specify new permissions, or use 'current' to keep current permissions.\n") % (file or _("new file")))
label.set_line_wrap(True)
label.set_use_markup(True)
dialog.vbox.pack_start(label, False, False)
if not path:
# file
hbox = gtk.HBox()
hbox.pack_start(gtk.Label(_("File: ")))
entry_file = gtk.Entry()
entry_file.set_text(file)
hbox.pack_start(entry_file)
dialog.vbox.pack_start(hbox, False, False)
# user
hbox = gtk.HBox()
hbox.pack_start(gtk.Label(_("User: ")))
entry_user = gtk.Entry()
entry_user.set_text(user)
hbox.pack_start(entry_user)
dialog.vbox.pack_start(hbox, False, False)
# group
hbox = gtk.HBox()
hbox.pack_start(gtk.Label(_("Group: ")))
entry_group = gtk.Entry()
entry_group.set_text(group)
hbox.pack_start(entry_group)
dialog.vbox.pack_start(hbox, False, False)
# perm
hbox = gtk.HBox()
hbox.pack_start(gtk.Label(_("Permissions: ")))
entry_perm = gtk.Entry()
entry_perm.set_text(perm)
hbox.pack_start(entry_perm)
dialog.vbox.pack_start(hbox, False, False)
dialog.show_all()
response = dialog.run()
if response != gtk.RESPONSE_OK:
dialog.destroy()
return
if not path:
newfile = entry_file.get_text()
else:
newfile = file
newuser = entry_user.get_text()
newgroup = entry_group.get_text()
newperm = entry_perm.get_text()
dialog.destroy()
self.permconfig.set(newfile, (newuser, newgroup, newperm, force))
if not path:
# adding new entry
iter = model.append()
model.set(iter, self.COLUMN_PATH, newfile)
model.set(iter, self.COLUMN_USER, newuser)
model.set(iter, self.COLUMN_GROUP, newgroup)
model.set(iter, self.COLUMN_PERM, newperm)
def option_changed(self, treeview, path, col, model):
"""Processes an option change"""
iter = model.get_iter(path)
param = model.get_value(iter, self.COLUMN_OPTION)
descr = model.get_value(iter, self.COLUMN_DESCR)
value = model.get_value(iter, self.COLUMN_VALUE)
# option is disabled?
if not value:
value = config.OPTION_DISABLED
callback, params = config.SETTINGS[param]
conf_def = self.msec_defaults[config.STANDARD_LEVEL]
conf_sec = self.msec_defaults[config.SECURE_LEVEL]
# Highlighting default options
def_start=""
def_end=""
sec_start=""
sec_end=""
if self.base_level == config.STANDARD_LEVEL:
def_start=""
def_end=""
elif self.base_level == config.SECURE_LEVEL:
sec_start=""
sec_end=""
val_def = conf_def.get(param)
val_sec = conf_sec.get(param)
# asks for new parameter value
dialog = gtk.Dialog(_("Select new value for %s") % (param),
self.window, 0,
(gtk.STOCK_OK, gtk.RESPONSE_OK,
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
# option title
label = gtk.Label("%s\n" % param)
label.set_use_markup(True)
# description
dialog.vbox.pack_start(label)
label = gtk.Label(_("%s\n\n\tCurrent value:\t\t\t%s\n\t%sStandard level value:\t%s%s\n\t%sSecure level value:\t\t%s%s\n") %
(descr, value,
def_start, val_def, def_end,
sec_start, val_sec, sec_end))
label.set_line_wrap(True)
label.set_use_markup(True)
dialog.vbox.pack_start(label)
dialog.vbox.pack_start(gtk.HSeparator())
# new value
hbox = gtk.HBox()
hbox.pack_start(gtk.Label(_("New value:")))
if '*' in params:
# string parameter
entry = gtk.Entry()
entry.set_text(value)
else:
# combobox parameter
entry = gtk.combo_box_new_text()
# add an item to disable a check
if config.OPTION_DISABLED not in params:
params.append(config.OPTION_DISABLED)
for item in params:
entry.append_text(item)
if value not in params:
entry.append_text(value)
params.append(value)
active = params.index(value)
entry.set_active(active)
hbox.pack_start(entry)
dialog.vbox.pack_start(hbox)
dialog.show_all()
response = dialog.run()
if response != gtk.RESPONSE_OK:
dialog.destroy()
return
# process new parameter
if '*' in params:
newval = entry.get_text()
else:
newval = entry.get_active_text()
dialog.destroy()
# update options
self.msecconfig.set(param, newval)
# is it different from default? if yes, change description to italic
doc = config.find_doc(self.msec, param, self.descriptions)
if self.option_is_changed(param, newval):
custom = pango.WEIGHT_BOLD
else:
custom = pango.WEIGHT_NORMAL
model.set(iter, self.COLUMN_VALUE, newval)
model.set(iter, self.COLUMN_DESCR, doc)
model.set(iter, self.COLUMN_CUSTOM, custom)
def signal_quit(self, s):
"""Quits via a signal"""
self.signals.put(s)
return True
def quit(self, widget, event=None):
"""Quits the application"""
# were there changes in configuration?
num_changes, allchanges, messages = self.check_for_changes(self.msecconfig, self.permconfig)
if num_changes > 0:
# there were changes
# asks for new parameter value
dialog = gtk.Dialog(_("Save your changes?"),
self.window, 0,
(_("_Cancel"), gtk.RESPONSE_CANCEL,
_("_Ignore"), gtk.RESPONSE_CLOSE,
_("_Save"), gtk.RESPONSE_OK))
# option title
label = gtk.Label(_("Do you want to save changes before closing?"))
dialog.vbox.pack_start(label, False, False, padding=DEFAULT_SPACING)
dialog.show_all()
response = dialog.run()
dialog.destroy()
if response == gtk.RESPONSE_OK:
ret = self.ok(widget)
if not ret:
# haven't saved
print "not saved"
return True
elif response == gtk.RESPONSE_CLOSE:
# leaving
gtk.main_quit()
else:
return True
print "Leaving.."
gtk.main_quit()
# {{{ usage
def usage():
"""Prints help message"""
print """Msec: Mandriva Security Center (%s).
Arguments to msecgui:
-h, --help displays this helpful message.
-d enable debugging messages.
-e, --embedded embed in MCC.
""" % version
# }}}
if __name__ == "__main__":
log_level = logging.INFO
PlugWindowID = None
# parse command line
try:
opt, args = getopt.getopt(sys.argv[1:], 'hde:', ['help', 'debug', 'embedded='])
except getopt.error:
usage()
sys.exit(1)
for o in opt:
# help
if o[0] == '-h' or o[0] == '--help':
usage()
sys.exit(0)
# list
elif o[0] == '-d' or o[0] == '--debug':
log_level = logging.DEBUG
elif o[0] == '-e' or o[0] == '--embedded':
try:
PlugWindowID = long(o[1])
except:
print >>sys.stderr, "Error: bad master window XID (%s)!" % o[1]
sys.exit(1)
# configuring logging
log = Log(interactive=True, log_syslog=False, log_file=True, log_level=log_level, log_path=config.SECURITYLOG)
# loading initial config
msec_config = config.MsecConfig(log, config=config.SECURITYCONF)
# loading permissions config
perm_conf = config.PermConfig(log, config=config.PERMCONF)
# loading exceptions
exceptions = config.ExceptionConfig(log, config=config.EXCEPTIONSCONF)
# creating an msec instance
msec = MSEC(log)
perms = PERMS(log)
log.info("Starting gui..")
gui = MsecGui(log, msec, perms, msec_config, perm_conf, exceptions, embed=PlugWindowID)
signal.signal(signal.SIGTERM, lambda s, f: gui.signal_quit(s))
gtk.main()