Merge remote-tracking branch 'origin/pr/208'

* origin/pr/208:
  Advertise qubes.VMExec support as a feature
  Install faster console scripts for Python code
  Add qubes.VMExec call, for running a single command
This commit is contained in:
Marek Marczykowski-Górecki 2020-01-27 05:36:02 +01:00
commit 142e220c7d
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
9 changed files with 165 additions and 5 deletions

View File

@ -30,6 +30,8 @@ etc/qubes-rpc/qubes.SuspendPreAll
etc/qubes-rpc/qubes.ConnectTCP
etc/qubes-rpc/qubes.VMShell
etc/qubes-rpc/qubes.VMRootShell
etc/qubes-rpc/qubes.VMExec
etc/qubes-rpc/qubes.VMExecGUI
etc/qubes-rpc/qubes.WaitForSession
etc/qubes-rpc/qubes.GetDate
etc/qubes-suspend-module-blacklist
@ -43,6 +45,7 @@ etc/qubes/rpc-config/qubes.SelectDirectory
etc/qubes/rpc-config/qubes.StartApp
etc/qubes/rpc-config/qubes.InstallUpdatesGUI
etc/qubes/rpc-config/qubes.VMShell+WaitForSession
etc/qubes/rpc-config/qubes.VMExecGUI
etc/qubes/suspend-post.d/README
etc/qubes/suspend-post.d/*.sh
etc/qubes/suspend-pre.d/README
@ -94,6 +97,7 @@ usr/bin/qubes-desktop-run
usr/bin/qubes-open
usr/bin/qubes-session-autostart
usr/bin/qubes-run-terminal
usr/bin/qubes-vmexec
usr/bin/qvm-copy
usr/bin/qvm-copy-to-vm
usr/bin/qvm-features-request

View File

@ -52,6 +52,7 @@ install:
install -t $(DESTDIR)$(QUBESRPCCMDDIR) \
qubes.Filecopy qubes.OpenInVM qubes.VMShell \
qubes.VMRootShell \
qubes.VMExec \
qubes.OpenURL \
qubes.SuspendPre qubes.SuspendPost qubes.GetAppmenus \
qubes.SuspendPreAll \
@ -69,6 +70,7 @@ install:
qubes.GetDate \
qubes.ShowInTerminal \
qubes.ConnectTCP
ln -s qubes.VMExec $(DESTDIR)$(QUBESRPCCMDDIR)/qubes.VMExecGUI
for config in *.config; do \
install -D -m 0644 "$$config" "$(DESTDIR)$(QUBESRPCCONFDIR)/$${config%.config}"; \
done

View File

@ -4,6 +4,7 @@
qvm-features-request qrexec=1
qvm-features-request os=Linux
qvm-features-request vmexec=1
if [ -x /usr/bin/qubes-gui ]; then
qvm-features-request gui=1

3
qubes-rpc/qubes.VMExec Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
exec /usr/bin/qubes-vmexec "$@"

View File

@ -0,0 +1 @@
wait-for-session=1

52
qubesagent/test_vmexec.py Normal file
View File

@ -0,0 +1,52 @@
from unittest import TestCase
from unittest.mock import patch, call
from qubesagent.vmexec import main, decode, DecodeError
class TestVmExec(TestCase):
def test_00_decode_simple(self):
self.assertEqual(decode('echo+Hello'), [b'echo', b'Hello'])
def test_01_decode_empty(self):
self.assertEqual(decode('echo+'), [b'echo', b''])
def test_02_decode_escaping(self):
self.assertEqual(decode('echo+Hello-20world'),
[b'echo', b'Hello world'])
self.assertEqual(decode('-0A-0D'),
[b'\n\r'])
self.assertEqual(decode('-2Fbin-2Fls'),
[b'/bin/ls'])
self.assertEqual(decode('ls+--la'),
[b'ls', b'-la'])
self.assertEqual(decode('ls+---61'),
[b'ls', b'-a'])
self.assertEqual(decode('ls+----help'),
[b'ls', b'--help'])
def test_03_decode_errors(self):
with self.assertRaises(DecodeError):
decode('illegal/slash')
with self.assertRaises(DecodeError):
decode('illegal-singledash')
with self.assertRaises(DecodeError):
decode('smalletters-0a-0d')
with self.assertRaises(DecodeError):
decode('incompletebyte-A')
with self.assertRaises(DecodeError):
decode('incomplete-Abyte')
with self.assertRaises(DecodeError):
decode('ls+---threeslashes')
def test_10_main_exec(self):
with patch('os.execvp') as mock_execvp:
main(['vmexec', 'ls+--la'])
self.assertEqual(mock_execvp.call_args_list, [
call(b'ls', [b'ls', b'-la'])])
def test_11_main_fail(self):
with self.assertRaises(SystemExit):
main(['vmexec'])
with self.assertRaises(SystemExit):
main(['vmexec', 'illegal/slash'])

59
qubesagent/vmexec.py Normal file
View File

@ -0,0 +1,59 @@
import sys
import os
import re
class DecodeError(ValueError):
pass
ESCAPE_RE = re.compile(br'--|-([A-F0-9]{2})')
def decode_part(part):
if not re.match(r'^[a-zA-Z0-9._-]*$', part):
raise DecodeError('illegal characters found')
part = part.encode('ascii')
# Check if no '-' remains outside of legal escape sequences.
if b'-' in ESCAPE_RE.sub(b'', part):
raise DecodeError("'-' can be used only in '-HH' or '--'")
def convert(m):
if m.group(0) == b'--':
return b'-'
num = int(m.group(1), 16)
return bytes([num])
return ESCAPE_RE.sub(convert, part)
def decode(arg):
'''
Decode the argument for executing. The format is as follows:
- individual parts are split by '+'
- bytes are escaped as '-HH' (where HH is hex code, capital letters only)
- literal '-' is encoded as '--'
- otherwise, only [a-zA-Z0-9._] are allowed
:param arg: argument, as a string
:returns: list of exec arguments (each as bytes)
'''
return [decode_part(part) for part in arg.split('+')]
def main(argv=sys.argv):
if len(argv) != 2:
print('This service requires exactly one argument', file=sys.stderr)
exit(1)
try:
command = decode(argv[1])
except DecodeError as e:
print('Decode error: {}'.format(e), file=sys.stderr)
exit(1)
os.execvp(command[0], command)
if __name__ == '__main__':
main()

View File

@ -549,6 +549,8 @@ rm -f %{name}-%{version}
%config(noreplace) /etc/qubes-rpc/qubes.GetAppmenus
%config(noreplace) /etc/qubes-rpc/qubes.ConnectTCP
%config(noreplace) /etc/qubes-rpc/qubes.VMShell
%config(noreplace) /etc/qubes-rpc/qubes.VMExec
%config(noreplace) /etc/qubes-rpc/qubes.VMExecGUI
%config(noreplace) /etc/qubes-rpc/qubes.VMRootShell
%config(noreplace) /etc/qubes-rpc/qubes.SuspendPre
%config(noreplace) /etc/qubes-rpc/qubes.SuspendPreAll
@ -574,6 +576,7 @@ rm -f %{name}-%{version}
%config(noreplace) /etc/qubes/rpc-config/qubes.StartApp
%config(noreplace) /etc/qubes/rpc-config/qubes.InstallUpdatesGUI
%config(noreplace) /etc/qubes/rpc-config/qubes.VMShell+WaitForSession
%config(noreplace) /etc/qubes/rpc-config/qubes.VMExecGUI
%dir /etc/qubes/autostart
%config(noreplace) /etc/default/grub.qubes
/etc/qubes/autostart/README.txt
@ -616,6 +619,7 @@ rm -f %{name}-%{version}
/usr/bin/qubes-session-autostart
/usr/bin/qvm-console
/usr/bin/qvm-connect-tcp
/usr/bin/qubes-vmexec
%dir /usr/lib/qubes
/usr/lib/qubes/prepare-suspend
/usr/lib/qubes/qfile-agent
@ -667,6 +671,8 @@ rm -f %{name}-%{version}
%{python3_sitelib}/qubesagent/__init__.py*
%{python3_sitelib}/qubesagent/firewall.py*
%{python3_sitelib}/qubesagent/test_firewall.py*
%{python3_sitelib}/qubesagent/vmexec.py*
%{python3_sitelib}/qubesagent/test_vmexec.py*
%{python3_sitelib}/qubesagent/xdg.py*
/usr/share/qubes/mime-override/globs

View File

@ -1,6 +1,40 @@
# vim: fileencoding=utf-8
import os
import setuptools
import setuptools.command.install
import re
CONSOLE_SCRIPTS = [
('qubes-firewall', 'qubesagent.firewall'),
('qubes-vmexec', 'qubesagent.vmexec'),
]
# create simple scripts that run much faster than "console entry points"
class CustomInstall(setuptools.command.install.install):
def run(self):
bin = os.path.join(self.root, "usr/bin")
try:
os.makedirs(bin)
except:
pass
for file, pkg in CONSOLE_SCRIPTS:
path = os.path.join(bin, file)
with open(path, "w") as f:
f.write(
"""#!/usr/bin/python3
from {} import main
import sys
if __name__ == '__main__':
sys.exit(main())
""".format(pkg))
os.chmod(path, 0o755)
setuptools.command.install.install.run(self)
if __name__ == '__main__':
setuptools.setup(
@ -14,9 +48,7 @@ if __name__ == '__main__':
packages=('qubesagent',),
entry_points={
'console_scripts': [
'qubes-firewall = qubesagent.firewall:main'
],
}
cmdclass={
'install': CustomInstall,
},
)