316 lines
11 KiB
Python
316 lines
11 KiB
Python
|
#!/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 <http://www.gnu.org/licenses/>.
|
||
|
'''
|
||
|
#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()
|