misc.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  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 library is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU Lesser General Public
  10. # License as published by the Free Software Foundation; either
  11. # version 2.1 of the License, or (at your option) any later version.
  12. #
  13. # This library 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 GNU
  16. # Lesser General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public
  19. # License along with this library; if not, see <https://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. SOCKNAME = '/var/run/qubesd.misc.sock'
  29. @qubes.api.method('qubes.FeaturesRequest', no_payload=True)
  30. @asyncio.coroutine
  31. def qubes_features_request(self):
  32. ''' qubes.FeaturesRequest handler
  33. VM (mostly templates) can request some features from dom0 for itself.
  34. Then dom0 (qubesd extension) may respect this request or ignore it.
  35. Technically, VM first write requested features into QubesDB in
  36. `/features-request/` subtree, then call this method. The method will
  37. dispatch 'features-request' event, which may be handled by
  38. appropriate extensions. Requests not explicitly handled by some
  39. extension are ignored.
  40. '''
  41. self.enforce(self.dest.name == 'dom0')
  42. self.enforce(not self.arg)
  43. prefix = '/features-request/'
  44. keys = self.src.untrusted_qdb.list(prefix)
  45. untrusted_features = {key[len(prefix):]:
  46. self.src.untrusted_qdb.read(key).decode('ascii', errors='strict')
  47. for key in keys}
  48. safe_set = string.ascii_letters + string.digits
  49. for untrusted_key in untrusted_features:
  50. untrusted_value = untrusted_features[untrusted_key]
  51. self.enforce(all((c in safe_set) for c in untrusted_value))
  52. yield from self.src.fire_event_async('features-request',
  53. untrusted_features=untrusted_features)
  54. self.app.save()
  55. @qubes.api.method('qubes.NotifyTools', no_payload=True)
  56. @asyncio.coroutine
  57. def qubes_notify_tools(self):
  58. '''
  59. Legacy version of qubes.FeaturesRequest, used by Qubes Windows Tools
  60. '''
  61. self.enforce(self.dest.name == 'dom0')
  62. self.enforce(not self.arg)
  63. untrusted_features = {}
  64. safe_set = string.ascii_letters + string.digits
  65. expected_features = ('qrexec', 'gui', 'gui-emulated', 'default-user',
  66. 'os')
  67. for feature in expected_features:
  68. untrusted_value = self.src.untrusted_qdb.read(
  69. '/qubes-tools/' + feature)
  70. if untrusted_value:
  71. untrusted_value = untrusted_value.decode('ascii',
  72. errors='strict')
  73. self.enforce(all((c in safe_set) for c in untrusted_value))
  74. untrusted_features[feature] = untrusted_value
  75. del untrusted_value
  76. yield from self.src.fire_event_async('features-request',
  77. untrusted_features=untrusted_features)
  78. self.app.save()
  79. @qubes.api.method('qubes.NotifyUpdates')
  80. @asyncio.coroutine
  81. def qubes_notify_updates(self, untrusted_payload):
  82. '''
  83. Receive VM notification about updates availability
  84. Payload contains a single integer - either 0 (no updates) or some
  85. positive value (some updates).
  86. '''
  87. untrusted_update_count = untrusted_payload.strip()
  88. self.enforce(untrusted_update_count.isdigit())
  89. # now sanitized
  90. update_count = int(untrusted_update_count)
  91. del untrusted_update_count
  92. # look for the nearest updateable VM up in the template chain
  93. updateable_template = getattr(self.src, 'template', None)
  94. while updateable_template is not None and \
  95. not updateable_template.updateable:
  96. updateable_template = getattr(updateable_template, 'template', None)
  97. if self.src.updateable:
  98. # Just trust information from VM itself
  99. self.src.features['updates-available'] = bool(update_count)
  100. self.app.save()
  101. elif updateable_template is not None:
  102. # Hint about updates availability in template
  103. # If template is running - it will notify about updates itself
  104. if updateable_template.is_running():
  105. return
  106. # Ignore no-updates info
  107. if update_count > 0:
  108. # If VM is outdated, updates were probably already installed
  109. # in the template - ignore info
  110. if self.src.storage.outdated_volumes:
  111. return
  112. updateable_template.features['updates-available'] = bool(
  113. update_count)
  114. self.app.save()