misc.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. # -*- encoding: utf8 -*-
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2017 Marek Marczykowski-Górecki
  6. # <marmarek@invisiblethingslab.com>
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License along
  19. # with this program; if not, see <http://www.gnu.org/licenses/>.
  20. ''' Interface for methods not being part of Admin API, but still handled by
  21. qubesd. '''
  22. import asyncio
  23. import string
  24. import qubes.api
  25. import qubes.api.admin
  26. import qubes.vm.dispvm
  27. class QubesMiscAPI(qubes.api.AbstractQubesAPI):
  28. @qubes.api.method('qubes.FeaturesRequest', no_payload=True)
  29. @asyncio.coroutine
  30. def qubes_features_request(self):
  31. ''' qubes.FeaturesRequest handler
  32. VM (mostly templates) can request some features from dom0 for itself.
  33. Then dom0 (qubesd extension) may respect this request or ignore it.
  34. Technically, VM first write requested features into QubesDB in
  35. `/features-request/` subtree, then call this method. The method will
  36. dispatch 'features-request' event, which may be handled by
  37. appropriate extensions. Requests not explicitly handled by some
  38. extension are ignored.
  39. '''
  40. assert self.dest.name == 'dom0'
  41. assert not self.arg
  42. prefix = '/features-request/'
  43. untrusted_features = {key[len(prefix):]:
  44. self.src.qdb.read(key).decode('ascii', errors='strict')
  45. for key in self.src.qdb.list(prefix)}
  46. safe_set = string.ascii_letters + string.digits
  47. for untrusted_key in untrusted_features:
  48. untrusted_value = untrusted_features[untrusted_key]
  49. assert all((c in safe_set) for c in untrusted_value)
  50. self.src.fire_event('features-request',
  51. untrusted_features=untrusted_features)
  52. self.app.save()
  53. @qubes.api.method('qubes.NotifyTools', no_payload=True)
  54. @asyncio.coroutine
  55. def qubes_notify_tools(self):
  56. '''
  57. Legacy version of qubes.FeaturesRequest, used by Qubes Windows Tools
  58. '''
  59. assert self.dest.name == 'dom0'
  60. assert not self.arg
  61. if getattr(self.src, 'template', None):
  62. self.src.log.warning(
  63. 'Ignoring qubes.NotifyTools for template-based VM')
  64. return
  65. # for now used only to check for the tools presence
  66. untrusted_version = self.src.qdb.read('/qubes-tools/version')
  67. # reserved for future use
  68. #untrusted_os = self.src.qdb.read('/qubes-tools/os')
  69. # qrexec agent presence (0 or 1)
  70. untrusted_qrexec = self.src.qdb.read('/qubes-tools/qrexec')
  71. # gui agent presence (0 or 1)
  72. untrusted_gui = self.src.qdb.read('/qubes-tools/gui')
  73. # default user for qvm-run etc
  74. # starting with Qubes 4.x ignored
  75. #untrusted_user = self.src.qdb.read('/qubes-tools/default-user')
  76. if untrusted_version is None:
  77. # tools didn't advertised its features; it's strange that this
  78. # service is called, but ignore it
  79. return
  80. # any suspicious string will raise exception here
  81. int(untrusted_version)
  82. del untrusted_version
  83. # untrusted_os - ignore for now
  84. if untrusted_qrexec is None:
  85. qrexec = False
  86. else:
  87. qrexec = bool(int(untrusted_qrexec))
  88. del untrusted_qrexec
  89. if untrusted_gui is None:
  90. gui = False
  91. else:
  92. gui = bool(int(untrusted_gui))
  93. del untrusted_gui
  94. # ignore default_user
  95. prev_qrexec = self.src.features.get('qrexec', False)
  96. # Let the tools to be able to enable *or disable*
  97. # each particular component
  98. self.src.features['qrexec'] = qrexec
  99. self.src.features['gui'] = gui
  100. self.app.save()
  101. if not prev_qrexec and qrexec:
  102. # if this is the first time qrexec was advertised, now can finish
  103. # template setup
  104. self.src.fire_event('template-postinstall')
  105. @qubes.api.method('qubes.NotifyUpdates')
  106. @asyncio.coroutine
  107. def qubes_notify_updates(self, untrusted_payload):
  108. '''
  109. Receive VM notification about updates availability
  110. Payload contains a single integer - either 0 (no updates) or some
  111. positive value (some updates).
  112. '''
  113. untrusted_update_count = untrusted_payload.strip()
  114. assert untrusted_update_count.isdigit()
  115. # now sanitized
  116. update_count = int(untrusted_update_count)
  117. del untrusted_update_count
  118. if self.src.updateable:
  119. # Just trust information from VM itself
  120. self.src.updates_available = bool(update_count)
  121. self.app.save()
  122. elif getattr(self.src, 'template', None) is not None:
  123. # Hint about updates availability in template
  124. # If template is running - it will notify about updates itself
  125. if self.src.template.is_running():
  126. return
  127. # Ignore no-updates info
  128. if update_count > 0:
  129. # If VM is outdated, updates were probably already installed
  130. # in the template - ignore info
  131. if self.src.storage.outdated_volumes:
  132. return
  133. self.src.template.updates_available = bool(update_count)
  134. self.app.save()