qubesd_query.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. #!/usr/bin/env python3.6
  2. import argparse
  3. import asyncio
  4. import signal
  5. import sys
  6. QUBESD_SOCK = '/var/run/qubesd.sock'
  7. MAX_PAYLOAD_SIZE = 65536
  8. parser = argparse.ArgumentParser(
  9. description='low-level qubesd interrogation tool')
  10. parser.add_argument('--connect', '-c', metavar='PATH',
  11. dest='socket',
  12. default=QUBESD_SOCK,
  13. help='path to qubesd UNIX socket (default: %(default)s)')
  14. parser.add_argument('--empty', '-e',
  15. dest='payload',
  16. action='store_false', default=True,
  17. help='do not read from stdin and send empty payload')
  18. parser.add_argument('--fail',
  19. dest='fail',
  20. action='store_true',
  21. help='Should non-OK qubesd response result in non-zero exit code')
  22. parser.add_argument('src', metavar='SRC',
  23. help='source qube')
  24. parser.add_argument('method', metavar='METHOD',
  25. help='method name')
  26. parser.add_argument('dest', metavar='DEST',
  27. help='destination qube')
  28. parser.add_argument('arg', metavar='ARGUMENT',
  29. nargs='?', default='',
  30. help='argument to method')
  31. def sighandler(loop, signame, coro):
  32. print('caught {}, exiting'.format(signame))
  33. coro.cancel()
  34. loop.stop()
  35. @asyncio.coroutine
  36. def qubesd_client(socket, payload, *args):
  37. '''
  38. Connect to qubesd, send request and passthrough response to stdout
  39. :param socket: path to qubesd socket
  40. :param payload: payload of the request
  41. :param args: request to qubesd
  42. :return:
  43. '''
  44. try:
  45. reader, writer = yield from asyncio.open_unix_connection(socket)
  46. except asyncio.CancelledError:
  47. return 1
  48. for arg in args:
  49. writer.write(arg.encode('ascii'))
  50. writer.write(b'\0')
  51. writer.write(payload)
  52. writer.write_eof()
  53. try:
  54. header_data = yield from reader.read(1)
  55. returncode = int(header_data)
  56. sys.stdout.buffer.write(header_data) # pylint: disable=no-member
  57. while not reader.at_eof():
  58. data = yield from reader.read(4096)
  59. sys.stdout.buffer.write(data) # pylint: disable=no-member
  60. sys.stdout.flush()
  61. return returncode
  62. except asyncio.CancelledError:
  63. return 1
  64. finally:
  65. writer.close()
  66. def main(args=None):
  67. args = parser.parse_args(args)
  68. loop = asyncio.get_event_loop()
  69. # pylint: disable=no-member
  70. if args.payload:
  71. # read one byte more to check for too long payload,
  72. # instead of silently truncating
  73. payload = sys.stdin.buffer.read(MAX_PAYLOAD_SIZE + 1)
  74. if len(payload) > MAX_PAYLOAD_SIZE:
  75. parser.error('Payload too long (max {})'.format(MAX_PAYLOAD_SIZE))
  76. # make sure to terminate, even if parser.error() would return
  77. # for some reason
  78. return 1
  79. else:
  80. payload = b''
  81. # pylint: enable=no-member
  82. coro = asyncio.ensure_future(qubesd_client(
  83. args.socket, payload,
  84. f'{args.method}+{args.arg} {args.src} name {args.dest}'))
  85. for signame in ('SIGINT', 'SIGTERM'):
  86. loop.add_signal_handler(getattr(signal, signame),
  87. sighandler, loop, signame, coro)
  88. try:
  89. returncode = loop.run_until_complete(coro)
  90. finally:
  91. loop.close()
  92. if args.fail:
  93. return returncode
  94. return 0
  95. if __name__ == '__main__':
  96. sys.exit(main())