2017-02-24 00:14:13 +01:00
|
|
|
# -*- encoding: utf8 -*-
|
|
|
|
#
|
|
|
|
# The Qubes OS Project, http://www.qubes-os.org
|
|
|
|
#
|
|
|
|
# Copyright (C) 2017 Marek Marczykowski-Górecki
|
|
|
|
# <marmarek@invisiblethingslab.com>
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Lesser General Public License along
|
|
|
|
# with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
2017-02-25 20:56:31 +01:00
|
|
|
'''
|
|
|
|
Main Qubes() class and related classes.
|
|
|
|
'''
|
|
|
|
|
|
|
|
import socket
|
2017-02-24 00:14:13 +01:00
|
|
|
import subprocess
|
|
|
|
|
2017-02-24 01:21:02 +01:00
|
|
|
import qubesmgmt.base
|
2017-02-24 00:14:13 +01:00
|
|
|
import qubesmgmt.vm
|
2017-03-12 00:40:14 +01:00
|
|
|
import qubesmgmt.label
|
2017-02-24 00:14:13 +01:00
|
|
|
import qubesmgmt.exc
|
2017-02-28 01:34:09 +01:00
|
|
|
import qubesmgmt.utils
|
2017-02-24 00:14:13 +01:00
|
|
|
|
|
|
|
QUBESD_SOCK = '/var/run/qubesd.sock'
|
|
|
|
BUF_SIZE = 4096
|
|
|
|
|
2017-02-25 20:56:31 +01:00
|
|
|
|
2017-02-24 00:14:13 +01:00
|
|
|
class VMCollection(object):
|
2017-02-25 20:56:31 +01:00
|
|
|
'''Collection of VMs objects'''
|
2017-02-24 00:14:13 +01:00
|
|
|
def __init__(self, app):
|
|
|
|
self.app = app
|
|
|
|
self._vm_list = None
|
2017-02-28 01:34:09 +01:00
|
|
|
self._vm_objects = {}
|
2017-02-24 00:14:13 +01:00
|
|
|
|
2017-02-24 01:38:47 +01:00
|
|
|
def clear_cache(self):
|
|
|
|
'''Clear cached list of VMs'''
|
|
|
|
self._vm_list = None
|
|
|
|
|
2017-02-24 00:14:13 +01:00
|
|
|
def refresh_cache(self, force=False):
|
2017-02-24 01:38:47 +01:00
|
|
|
'''Refresh cached list of VMs'''
|
2017-02-24 00:14:13 +01:00
|
|
|
if not force and self._vm_list is not None:
|
|
|
|
return
|
2017-02-24 00:40:07 +01:00
|
|
|
vm_list_data = self.app.qubesd_call(
|
2017-02-24 00:14:13 +01:00
|
|
|
'dom0',
|
|
|
|
'mgmt.vm.List'
|
|
|
|
)
|
|
|
|
new_vm_list = {}
|
|
|
|
# FIXME: this will probably change
|
|
|
|
for vm_data in vm_list_data.splitlines():
|
2017-02-24 01:00:06 +01:00
|
|
|
vm_name, props = vm_data.decode('ascii').split(' ', 1)
|
|
|
|
props = props.split(' ')
|
2017-02-24 00:14:13 +01:00
|
|
|
new_vm_list[vm_name] = dict(
|
2017-02-24 01:00:06 +01:00
|
|
|
[vm_prop.split('=', 1) for vm_prop in props])
|
2017-02-24 00:14:13 +01:00
|
|
|
|
|
|
|
self._vm_list = new_vm_list
|
2017-03-01 15:24:36 +01:00
|
|
|
for name, vm in list(self._vm_objects.items()):
|
2017-02-28 01:34:09 +01:00
|
|
|
if vm.name not in self._vm_list:
|
|
|
|
# VM no longer exists
|
|
|
|
del self._vm_objects[name]
|
|
|
|
elif vm.__class__.__name__ != self._vm_list[vm.name]['class']:
|
|
|
|
# VM class have changed
|
|
|
|
del self._vm_objects[name]
|
|
|
|
# TODO: some generation ID, to detect VM re-creation
|
|
|
|
elif name != vm.name:
|
|
|
|
# renamed
|
|
|
|
self._vm_objects[vm.name] = vm
|
|
|
|
del self._vm_objects[name]
|
2017-02-24 00:14:13 +01:00
|
|
|
|
|
|
|
def __getitem__(self, item):
|
|
|
|
if item not in self:
|
|
|
|
raise KeyError(item)
|
2017-02-28 01:34:09 +01:00
|
|
|
if item not in self._vm_objects:
|
|
|
|
cls = qubesmgmt.utils.get_entry_point_one('qubesmgmt.vm',
|
|
|
|
self._vm_list[item]['class'])
|
|
|
|
self._vm_objects[item] = cls(self.app, item)
|
|
|
|
return self._vm_objects[item]
|
2017-02-24 00:14:13 +01:00
|
|
|
|
|
|
|
def __contains__(self, item):
|
|
|
|
self.refresh_cache()
|
|
|
|
return item in self._vm_list
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
self.refresh_cache()
|
|
|
|
for vm in self._vm_list:
|
|
|
|
yield self[vm]
|
|
|
|
|
|
|
|
def keys(self):
|
2017-02-25 20:56:31 +01:00
|
|
|
'''Get list of VM names.'''
|
2017-02-24 00:14:13 +01:00
|
|
|
self.refresh_cache()
|
|
|
|
return self._vm_list.keys()
|
|
|
|
|
|
|
|
|
2017-03-12 00:40:14 +01:00
|
|
|
|
2017-02-24 01:21:02 +01:00
|
|
|
class QubesBase(qubesmgmt.base.PropertyHolder):
|
2017-02-24 00:14:13 +01:00
|
|
|
'''Main Qubes application'''
|
|
|
|
|
|
|
|
#: domains (VMs) collection
|
|
|
|
domains = None
|
2017-03-12 00:40:14 +01:00
|
|
|
#: labels collection
|
|
|
|
labels = None
|
2017-02-24 00:14:13 +01:00
|
|
|
|
|
|
|
def __init__(self):
|
2017-03-11 00:58:53 +01:00
|
|
|
super(QubesBase, self).__init__(self, 'mgmt.property.', 'dom0')
|
2017-02-24 00:14:13 +01:00
|
|
|
self.domains = VMCollection(self)
|
2017-03-12 00:40:14 +01:00
|
|
|
self.labels = qubesmgmt.label.LabelsCollection(self)
|
2017-02-24 00:14:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
class QubesLocal(QubesBase):
|
2017-02-25 20:56:31 +01:00
|
|
|
'''Application object communicating through local socket.
|
|
|
|
|
|
|
|
Used when running in dom0.
|
|
|
|
'''
|
2017-02-24 00:40:07 +01:00
|
|
|
def qubesd_call(self, dest, method, arg=None, payload=None):
|
2017-02-24 00:14:13 +01:00
|
|
|
try:
|
|
|
|
client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
|
|
client_socket.connect(QUBESD_SOCK)
|
|
|
|
except IOError:
|
|
|
|
# TODO:
|
|
|
|
raise
|
|
|
|
|
|
|
|
# src, method, dest, arg
|
|
|
|
for call_arg in ('dom0', method, dest, arg):
|
2017-02-27 20:15:24 +01:00
|
|
|
if call_arg is not None:
|
|
|
|
client_socket.sendall(call_arg.encode('ascii'))
|
2017-02-24 00:14:13 +01:00
|
|
|
client_socket.sendall(b'\0')
|
|
|
|
if payload is not None:
|
|
|
|
client_socket.sendall(payload)
|
|
|
|
|
2017-02-27 20:15:24 +01:00
|
|
|
client_socket.shutdown(socket.SHUT_WR)
|
|
|
|
|
2017-03-11 01:16:10 +01:00
|
|
|
return_data = client_socket.makefile('rb').read()
|
2017-02-24 00:14:13 +01:00
|
|
|
return self._parse_qubesd_response(return_data)
|
|
|
|
|
|
|
|
|
|
|
|
class QubesRemote(QubesBase):
|
2017-02-25 20:56:31 +01:00
|
|
|
'''Application object communicating through qrexec services.
|
|
|
|
|
|
|
|
Used when running in VM.
|
|
|
|
'''
|
2017-02-24 00:40:07 +01:00
|
|
|
def qubesd_call(self, dest, method, arg=None, payload=None):
|
2017-02-24 00:14:13 +01:00
|
|
|
service_name = method
|
|
|
|
if arg is not None:
|
|
|
|
service_name += '+' + arg
|
|
|
|
p = subprocess.Popen(['qrexec-client-vm', dest, service_name],
|
|
|
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE)
|
|
|
|
(stdout, stderr) = p.communicate(payload)
|
|
|
|
if p.returncode != 0:
|
|
|
|
# TODO: use dedicated exception
|
|
|
|
raise qubesmgmt.exc.QubesException('Service call error: %s',
|
|
|
|
stderr.decode())
|
|
|
|
|
|
|
|
return self._parse_qubesd_response(stdout)
|