#!/usr/bin/python3 ''' Copyright 2020 Ian Santopietro (ian@system76.com) This file is part of Repolib. Repolib 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, either version 3 of the License, or (at your option) any later version. Repolib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Repolib. If not, see . ''' #pylint: skip-file import shutil import subprocess import gi from gi.repository import GObject, GLib from pathlib import Path import dbus import dbus.service import dbus.mainloop.glib import time import repolib class RepolibException(dbus.DBusException): _dbus_error_name = 'org.pop_os.repolib.RepolibException' class PermissionDeniedByPolicy(dbus.DBusException): _dbus_error_name = 'org.pop_os.repolib.PermissionDeniedByPolicy' class AptException(Exception): pass class Repo(dbus.service.Object): def __init__(self, conn=None, object_path=None, bus_name=None): dbus.service.Object.__init__(self, conn, object_path, bus_name) # These are used by PolKit to check privileges self.dbus_info = None self.polkit = None self.enforce_polkit = True try: self.system_repo = repolib.SystemSource() except: self.system_repo = None self.source = None self.sources_dir = Path('/etc/apt/sources.list.d') self.keys_dir = Path('/etc/apt/trusted.gpg.d') @dbus.service.method( "org.pop_os.repolib.Interface", in_signature='as', out_signature='', sender_keyword='sender', connection_keyword='conn' ) def add_apt_signing_key(self, cmd, sender=None, conn=None): self._check_polkit_privilege( sender, conn, 'org.pop_os.repolib.modifysources' ) print(cmd) key_path = str(cmd.pop(-1)) with open(key_path, mode='wb') as keyfile: try: subprocess.run(cmd, check=True, stdout=keyfile) except subprocess.CalledProcessError as e: raise e @dbus.service.method( "org.pop_os.repolib.Interface", in_signature='ss', out_signature='', sender_keyword='sender', connection_keyword='conn' ) def install_signing_key(self, src, dest, sender=None, conn=None): self._check_polkit_privilege( sender, conn, 'org.pop_os.repolib.modifysources' ) shutil.copy2(src, dest) @dbus.service.method( "org.pop_os.repolib.Interface", in_signature='s', out_signature='', sender_keyword='sender', connection_keyword='conn' ) def delete_signing_key(self, src, sender=None, conn=None): self._check_polkit_privilege( sender, conn, 'org.pop_os.repolib.modifysources' ) key_path = Path(src) key_path.unlink() @dbus.service.method( "org.pop_os.repolib.Interface", in_signature='s', out_signature='', sender_keyword='sender', connection_keyword='conn' ) def delete_prefs_file(self, src, sender=None, conn=None): self._check_polkit_privilege( sender, conn, 'org.pop_os.repolib.modifysources' ) prefs_path = Path(src) prefs_path.unlink() @dbus.service.method( "org.pop_os.repolib.Interface", in_signature='ss', out_signature='', sender_keyword='sender', connection_keyword='conn' ) def output_prefs_to_disk(self, path, contents, sender=None, conn=None): self._check_polkit_privilege( sender, conn, 'org.pop_os.repolib.modifysources' ) full_path = Path(path) with open(full_path, mode='w') as output_file: output_file.write(contents) @dbus.service.method( "org.pop_os.repolib.Interface", in_signature='ss', out_signature='', sender_keyword='sender', connection_keyword='conn' ) def output_file_to_disk(self, filename, source, sender=None, conn=None): self._check_polkit_privilege( sender, conn, 'org.pop_os.repolib.modifysources' ) full_path = self.sources_dir / filename with open(full_path, mode='w') as output_file: output_file.write(source) @dbus.service.method( "org.pop_os.repolib.Interface", in_signature='ss', out_signature='', sender_keyword='sender', connection_keyword='conn' ) def backup_alt_file(self, alt_file, save_file, sender=None, conn=None): self._check_polkit_privilege( sender, conn, 'org.pop_os.repolib.modifysources' ) alt_path = self.sources_dir / alt_file save_path = self.sources_dir / save_file if alt_path.exists(): alt_path.rename(save_path) @dbus.service.method( "org.pop_os.repolib.Interface", in_signature='s', out_signature='', sender_keyword='sender', connection_keyword='conn' ) def delete_source_file(self, filename, sender=None, conn=None): self._check_polkit_privilege( sender, conn, 'org.pop_os.repolib.modifysources' ) source_file = self.sources_dir / filename source_file.unlink() @dbus.service.method( "org.pop_os.repolib.Interface", in_signature='', out_signature='', sender_keyword='sender', connection_keyword='conn' ) def exit(self, sender=None, conn=None): mainloop.quit() @dbus.service.method( "org.pop_os.repolib.Interface", in_signature='b', out_signature='b', sender_keyword='sender', connection_keyword='conn' ) def set_system_source_code_enabled(self, enabled, sender=None, conn=None): """ Enable or disable source code in the system source. Arguments: enabled (bool): The new state to set, True = Enabled. """ self._check_polkit_privilege( sender, conn, 'org.pop_os.repolib.modifysources' ) if self.system_repo: self.system_repo.load_from_file() new_types = [repolib.util.AptSourceType.BINARY] if enabled: new_types.append(repolib.util.AptSourceType.SOURCE) self.system_repo.types = new_types self.system_repo.save_to_disk() return enabled return False @dbus.service.method( "org.pop_os.repolib.Interface", in_signature='sb', out_signature='b', sender_keyword='sender', connection_keyword='conn' ) def set_system_comp_enabled(self, comp, enable, sender=None, conn=None): """ Enable or disable a component in the system source. Arguments: comp (str): the component to set enable (bool): The new state to set, True = Enabled. """ self._check_polkit_privilege( sender, conn, 'org.pop_os.repolib.modifysources' ) if self.system_repo: self.system_repo.load_from_file() self.system_repo.set_component_enabled(component=comp, enabled=enable) self.system_repo.save_to_disk() return True return False @dbus.service.method( "org.pop_os.repolib.Interface", in_signature='sb', out_signature='b', sender_keyword='sender', connection_keyword='conn' ) def set_system_suite_enabled(self, suite, enable, sender=None, conn=None): """ Enable or disable a suite in the system source. Arguments: suite (str): the suite to set enable (bool): The new state to set, True = Enabled. """ self._check_polkit_privilege( sender, conn, 'org.pop_os.repolib.modifysources' ) if self.system_repo: self.system_repo.load_from_file() self.system_repo.set_suite_enabled(suite=suite, enabled=enable) self.system_repo.save_to_disk() return True return False @classmethod def _log_in_file(klass, filename, string): date = time.asctime(time.localtime()) ff = open(filename, "a") ff.write("%s : %s\n" %(date,str(string))) ff.close() @classmethod def _strip_source_line(self, source): source = source.replace("#", "# ") source = source.replace("[", "") source = source.replace("]", "") source = source.replace("'", "") source = source.replace(" ", " ") return source def _check_polkit_privilege(self, sender, conn, privilege): # from jockey '''Verify that sender has a given PolicyKit privilege. sender is the sender's (private) D-BUS name, such as ":1:42" (sender_keyword in @dbus.service.methods). conn is the dbus.Connection object (connection_keyword in @dbus.service.methods). privilege is the PolicyKit privilege string. This method returns if the caller is privileged, and otherwise throws a PermissionDeniedByPolicy exception. ''' if sender is None and conn is None: # called locally, not through D-BUS return if not self.enforce_polkit: # that happens for testing purposes when running on the session # bus, and it does not make sense to restrict operations here return # get peer PID if self.dbus_info is None: self.dbus_info = dbus.Interface(conn.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus/Bus', False), 'org.freedesktop.DBus') pid = self.dbus_info.GetConnectionUnixProcessID(sender) # query PolicyKit if self.polkit is None: self.polkit = dbus.Interface(dbus.SystemBus().get_object( 'org.freedesktop.PolicyKit1', '/org/freedesktop/PolicyKit1/Authority', False), 'org.freedesktop.PolicyKit1.Authority') try: # we don't need is_challenge return here, since we call with AllowUserInteraction (is_auth, _, details) = self.polkit.CheckAuthorization( ('unix-process', {'pid': dbus.UInt32(pid, variant_level=1), 'start-time': dbus.UInt64(0, variant_level=1)}), privilege, {'': ''}, dbus.UInt32(1), '', timeout=600) except dbus.DBusException as e: if e._dbus_error_name == 'org.freedesktop.DBus.Error.ServiceUnknown': # polkitd timed out, connect again self.polkit = None return self._check_polkit_privilege(sender, conn, privilege) else: raise if not is_auth: Repo._log_in_file('/tmp/repolib.log','_check_polkit_privilege: sender %s on connection %s pid %i is not authorized for %s: %s' % (sender, conn, pid, privilege, str(details))) raise PermissionDeniedByPolicy(privilege) if __name__ == '__main__': dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SystemBus() name = dbus.service.BusName("org.pop_os.repolib", bus) object = Repo(bus, '/Repo') mainloop = GLib.MainLoop() mainloop.run()