Permalink
| #!/usr/bin/python3 | |
| # MintNanny | |
| # Clement Lefebvre <clem@linuxmint.com> | |
| # | |
| # This program is free software; you can redistribute it and/or | |
| # modify it under the terms of the GNU General Public License | |
| # as published by the Free Software Foundation; Version 2 | |
| # of the License. | |
| import os | |
| import re | |
| import sys | |
| import gettext | |
| import fileinput | |
| import gi | |
| gi.require_version("Gtk", "3.0") | |
| from gi.repository import Gtk, Gdk | |
| # i18n | |
| gettext.install("mintnanny", "/usr/share/linuxmint/locale") | |
| class MintNanny(): | |
| def __init__(self): | |
| # Set the Glade file | |
| gladefile = "/usr/share/linuxmint/mintnanny/mintnanny.ui" | |
| builder = Gtk.Builder() | |
| builder.add_from_file(gladefile) | |
| self.window = builder.get_object("main_window") | |
| self.window.set_title(_("Domain Blocker")) | |
| self.window.set_icon_name("mintnanny") | |
| # the treeview | |
| column = Gtk.TreeViewColumn(_("Blocked domains"), Gtk.CellRendererText(), text=0) | |
| column.set_sort_column_id(0) | |
| column.set_resizable(True) | |
| self.treeview = builder.get_object("treeview_domains") | |
| self.treeview.append_column(column) | |
| self.treeview.set_headers_clickable(True) | |
| self.treeview.set_reorderable(False) | |
| self.treeview.show() | |
| self.model = Gtk.TreeStore(str) | |
| self.model.set_sort_column_id(0, Gtk.SortType.ASCENDING) | |
| self.treeview.set_model(self.model) | |
| # Get the list of allowed domains | |
| with open("/etc/hosts") as f: | |
| for line in f: | |
| if '0.0.0.0' in line or 'blocked by mintNanny' in line: | |
| elements = line.split() | |
| if len(elements) > 1: | |
| domain = elements[1] | |
| iter = self.model.insert_before(None, None) | |
| self.model.set_value(iter, 0, domain) | |
| self.window.connect("delete_event", Gtk.main_quit) | |
| builder.get_object("toolbutton_add").connect("clicked", self.add_domain) | |
| self.remove_button = builder.get_object("toolbutton_remove") | |
| self.remove_button.connect("clicked", self.remove_domain) | |
| self.remove_button.set_sensitive(False) | |
| self.treeview.get_selection().connect("changed", self.on_domain_selected) | |
| self.window.show_all() | |
| def ask_domain_name(self): | |
| dialogWindow = Gtk.MessageDialog(self.window, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL, _("Please type the domain name you want to block")) | |
| dialogWindow.set_title(_("Domain name")) | |
| dialogBox = dialogWindow.get_content_area() | |
| entry = Gtk.Entry() | |
| entry.set_activates_default(True) | |
| box = Gtk.Box() | |
| box.pack_start(entry, True, True, 12) | |
| dialogBox.pack_start(box, True, True, 0) | |
| okButton = dialogWindow.get_widget_for_response(response_id=Gtk.ResponseType.OK) | |
| okButton.set_can_default(True) | |
| okButton.grab_default() | |
| dialogWindow.show_all() | |
| response = dialogWindow.run() | |
| text = entry.get_text() | |
| dialogWindow.destroy() | |
| if (response == Gtk.ResponseType.OK) and (text != ''): | |
| return text | |
| else: | |
| return None | |
| def add_domain(self, widget): | |
| domain = self.ask_domain_name() | |
| if domain is None or domain == '': | |
| # Take no action on empty input | |
| return | |
| domain = re.sub(r'\s', '', domain) | |
| if domain.startswith('www.'): | |
| domain = domain[4:] | |
| if not self.is_valid_domain(domain): | |
| # User has passed an invalid domain (one that contains invalid characters) | |
| # Display an error dialog to inform them why we're not adding it to the list | |
| dlg = Gtk.MessageDialog(type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, message_format=_("Invalid Domain")) | |
| dlg.set_transient_for(self.window) | |
| desc1 = _("%s is not a valid domain name." % domain) | |
| desc2 = _("Domain names must start and end with a letter or a digit, and can only contain letters, digits, dots and hyphens.") | |
| desc3 = _("Example: my.number1domain.com") | |
| dlg.format_secondary_text("%s\n\n%s\n\n%s" % (desc1, desc2, desc3)) | |
| dlg.run() | |
| dlg.destroy() | |
| return | |
| prefixes = [""] | |
| if len(domain.split(".")) == 2: | |
| # domain in the form 'domainname.extension' | |
| prefixes.append("www.") | |
| for prefix in prefixes: | |
| full_domain = "%s%s" % (prefix, domain) | |
| iter = self.model.insert_before(None, None) | |
| self.model.set_value(iter, 0, full_domain) | |
| os.system("echo \"127.0.0.1 %s # blocked by mintNanny\" >> /etc/hosts" % full_domain) | |
| def on_domain_selected(self, selection): | |
| model, treeiter = selection.get_selected() | |
| self.remove_button.set_sensitive(treeiter != None) | |
| def remove_domain(self, widget): | |
| selection = self.treeview.get_selection() | |
| (model, iter) = selection.get_selected() | |
| if (iter != None): | |
| domain = model.get_value(iter, 0) | |
| for line_number, line in enumerate(fileinput.input('/etc/hosts', inplace=1)): | |
| found = False | |
| if '0.0.0.0' in line or 'blocked by mintNanny' in line: | |
| elements = line.split() | |
| if len(elements) > 1: | |
| if elements[1] == domain: | |
| found = True | |
| if not found: | |
| sys.stdout.write(line) | |
| model.remove(iter) | |
| def is_valid_domain(self, domain): | |
| # Quick sanity check | |
| if domain == '' or "." not in domain: | |
| return False | |
| # The following is based on RFC 952 (https://tools.ietf.org/html/rfc952) | |
| # and section 2.1 of RFC 1123 (https://tools.ietf.org/html/rfc1123#page-13) | |
| # Also see sections 2.3.1 and 2.3.4 of RFC 1035 (http://tools.ietf.org/html/rfc1035) | |
| # Quick regex match to check the domain name's sanity | |
| # Note: This enforces that the domain name starts with a letter or a digit | |
| # This does NOT enforce the label size limits (63 characters max in-between dots) | |
| regex = re.compile('^[A-Za-z0-9][A-Za-z0-9\-\.]+$') | |
| if not regex.match(domain): | |
| return False | |
| # A domain name MUST end with an alphanumeric character | |
| # At this point we're certain that the string only contains alphanumeric characters or hyphens and dots. | |
| # So we just need to check that it doesn't end with a hyphen or a dot | |
| if domain.endswith('-') or domain.endswith('.'): | |
| return False | |
| # Domain names have a length limit of 255 characters | |
| if len(domain) > 255: | |
| return False | |
| return True | |
| if __name__ == "__main__": | |
| # If no backup of /etc/hosts was made, make one | |
| if not os.path.exists("/etc/hosts.mintnanny.backup"): | |
| os.system("cp /etc/hosts /etc/hosts.mintnanny.backup") | |
| MintNanny() | |
| Gtk.main() |