# -*- encoding: utf8 -*- # # The Qubes OS Project, http://www.qubes-os.org # # Copyright (C) 2017 Marek Marczykowski-Górecki # # # 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; either version 2 of the License, or # (at your option) any later version. # # This program 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 this program; if not, see . ''' Interface for methods not being part of Admin API, but still handled by qubesd. ''' import asyncio import string import qubes.api import qubes.api.admin import qubes.vm.dispvm QUBESD_MISC_SOCK = '/var/run/qubesd.misc.sock' class QubesMiscAPI(qubes.api.AbstractQubesAPI): @qubes.api.method('qubes.FeaturesRequest', no_payload=True) @asyncio.coroutine def qubes_features_request(self): ''' qubes.FeaturesRequest handler VM (mostly templates) can request some features from dom0 for itself. Then dom0 (qubesd extension) may respect this request or ignore it. Technically, VM first write requested features into QubesDB in `/features-request/` subtree, then call this method. The method will dispatch 'features-request' event, which may be handled by appropriate extensions. Requests not explicitly handled by some extension are ignored. ''' assert self.dest.name == 'dom0' assert not self.arg prefix = '/features-request/' keys = [key.decode('ascii', errors='strict') for key in self.src.qdb.list(prefix)] untrusted_features = {key[len(prefix):]: self.src.qdb.read(key).decode('ascii', errors='strict') for key in keys} safe_set = string.ascii_letters + string.digits for untrusted_key in untrusted_features: untrusted_value = untrusted_features[untrusted_key] assert all((c in safe_set) for c in untrusted_value) self.src.fire_event('features-request', untrusted_features=untrusted_features) self.app.save() @qubes.api.method('qubes.NotifyTools', no_payload=True) @asyncio.coroutine def qubes_notify_tools(self): ''' Legacy version of qubes.FeaturesRequest, used by Qubes Windows Tools ''' assert self.dest.name == 'dom0' assert not self.arg untrusted_features = {} safe_set = string.ascii_letters + string.digits expected_features = ('qrexec', 'gui', 'default-user') for feature in expected_features: untrusted_value = self.src.qdb.read('/qubes-tools/' + feature) if untrusted_value: untrusted_value = untrusted_value.decode('ascii', errors='strict') assert all((c in safe_set) for c in untrusted_value) untrusted_features[feature] = untrusted_value del untrusted_value self.src.fire_event('features-request', untrusted_features=untrusted_features) self.app.save() @qubes.api.method('qubes.NotifyUpdates') @asyncio.coroutine def qubes_notify_updates(self, untrusted_payload): ''' Receive VM notification about updates availability Payload contains a single integer - either 0 (no updates) or some positive value (some updates). ''' untrusted_update_count = untrusted_payload.strip() assert untrusted_update_count.isdigit() # now sanitized update_count = int(untrusted_update_count) del untrusted_update_count if self.src.updateable: # Just trust information from VM itself self.src.features['updates-available'] = bool(update_count) self.app.save() elif getattr(self.src, 'template', None) is not None: # Hint about updates availability in template # If template is running - it will notify about updates itself if self.src.template.is_running(): return # Ignore no-updates info if update_count > 0: # If VM is outdated, updates were probably already installed # in the template - ignore info if self.src.storage.outdated_volumes: return self.src.template.features['updates-available'] = bool( update_count) self.app.save()