Unverified Commit 5b1c1515 authored by meskio's avatar meskio
Browse files

[feat] use subcommands in the cli

Reorganize all the commands to don't use '--' but parse each subcommand
separately.
parent 24203c37
......@@ -18,27 +18,20 @@
"""
Bitmask Command Line interface: zmq client.
"""
import json
import sys
import getpass
import argparse
from colorama import init as color_init
from colorama import Fore
from twisted.internet import reactor
from txzmq import ZmqEndpoint, ZmqEndpointType
from txzmq import ZmqFactory, ZmqREQConnection
from txzmq import ZmqRequestTimeoutError
from twisted.internet import reactor, defer
from leap.bitmask.core import ENDPOINT
from leap.keymanager.validation import ValidationLevels
from leap.bitmask.cli.eip import Eip
from leap.bitmask.cli.keys import Keys
from leap.bitmask.cli.mail import Mail
from leap.bitmask.cli.command import Command
from leap.bitmask.cli.user import User
class BitmaskCLI(object):
def __init__(self):
parser = argparse.ArgumentParser(
usage='''bitmask_cli <command> [<args>]
class BitmaskCLI(Command):
usage = '''bitmask_cli <command> [<args>]
Controls the Bitmask application.
......@@ -55,347 +48,53 @@ GENERAL COMMANDS:
launch launch the Bitmask backend daemon
shutdown shutdown Bitmask backend daemon
status displays general status about the running Bitmask services
debug show some debug info about bitmask-core
''', epilog=("Use 'bitmask_cli <command> --help' to learn more "
"about each command."))
parser.add_argument('command', help='Subcommand to run')
# parse_args defaults to [1:] for args, but you need to
# exclude the rest of the args too, or validation will fail
args = parser.parse_args(sys.argv[1:2])
self.args = args
self.subargs = None
stats show some debug info about bitmask-core
help show this help message
if not hasattr(self, args.command):
print 'Unrecognized command'
parser.print_help()
exit(1)
'''
epilog = ("Use 'bitmask_cli <command> help' to learn more "
"about each command.")
commands = ['shutdown', 'status', 'stats']
# use dispatch pattern to invoke method with same name
getattr(self, args.command)()
def user(self, raw_args):
user = User()
return user.execute(raw_args)
def user(self):
parser = argparse.ArgumentParser(
description=('Handles Bitmask accounts: creation, authentication '
'and modification'),
prog='bitmask_cli user')
parser.add_argument('username', nargs='?',
help='username ID, in the form <user@example.org>')
parser.add_argument('--create', action='store_true',
help='register a new user, if possible')
parser.add_argument('--authenticate', action='store_true',
help='logs in against the provider')
parser.add_argument('--logout', action='store_true',
help='ends any active session with the provider')
parser.add_argument('--active', action='store_true',
help='shows the active user, if any')
# now that we're inside a subcommand, ignore the first
# TWO argvs, ie the command (bitmask_cli) and the subcommand (user)
args = parser.parse_args(sys.argv[2:])
self.subargs = args
def mail(self, raw_args):
mail = Mail()
return mail.execute(raw_args)
def mail(self):
parser = argparse.ArgumentParser(
description='Bitmask Encrypted Mail service',
prog='bitmask_cli mail')
parser.add_argument('--start', action='store_true',
help='tries to start the mail service')
parser.add_argument('--stop', action='store_true',
help='stops the mail service if running')
parser.add_argument('--status', action='store_true',
help='displays status about the mail service')
parser.add_argument('--enable', action='store_true')
parser.add_argument('--disable', action='store_true')
parser.add_argument('--get-token', action='store_true',
help='returns token for the mail service')
parser.add_argument('--get-smtp-certificate', action='store_true',
help='downloads a new smtp certificate')
parser.add_argument('--check-smtp-certificate', action='store_true',
help='downloads a new smtp certificate '
'(NOT IMPLEMENTED)')
def eip(self, raw_args):
eip = Eip()
return eip.execute(raw_args)
args = parser.parse_args(sys.argv[2:])
self.subargs = args
def eip(self):
parser = argparse.ArgumentParser(
description='Encrypted Internet Proxy service',
prog='bitmask_cli eip')
parser.add_argument('--start', action='store_true',
help='Start service')
parser.add_argument('--stop', action='store_true', help='Stop service')
parser.add_argument('--status', action='store_true',
help='Display status about service')
parser.add_argument('--enable', action='store_true')
parser.add_argument('--disable', action='store_true')
args = parser.parse_args(sys.argv[2:])
self.subargs = args
def keys(self):
parser = argparse.ArgumentParser(
description='Bitmask Keymanager management service',
prog='bitmask_cli keys')
parser.add_argument('--list', action='store_true',
help='List all known keys')
parser.add_argument('--export', action='store_true',
help='Export the given key')
parser.add_argument('--import', action='store', metavar='file',
dest='imprt',
help='Import a key from the file')
parser.add_argument('--delete', action='store_true',
help='Delete the given key')
parser.add_argument('--private', action='store_true',
help='Use private keys (by default uses public)')
parser.add_argument('--validation', choices=list(ValidationLevels),
default='Fingerprint',
help='Validation level for the key')
parser.add_argument('address', nargs='?',
help='email address of the key')
args = parser.parse_args(sys.argv[2:])
self.subargs = args
def keys(self, raw_args):
keys = Keys()
return keys.execute(raw_args)
# Single commands
def launch(self):
pass
def shutdown(self):
pass
def status(self):
pass
def version(self):
pass
def debug(self):
pass
def get_zmq_connection():
zf = ZmqFactory()
e = ZmqEndpoint(ZmqEndpointType.connect, ENDPOINT)
return ZmqREQConnection(zf, e)
def error(msg, stop=False):
print Fore.RED + "[!] %s" % msg + Fore.RESET
if stop:
reactor.stop()
else:
sys.exit(1)
def timeout_handler(failure, stop_reactor=True):
# TODO ---- could try to launch the bitmask daemon here and retry
if failure.trap(ZmqRequestTimeoutError) == ZmqRequestTimeoutError:
print (Fore.RED + "[ERROR] Timeout contacting the bitmask daemon. "
"Is it running?" + Fore.RESET)
reactor.stop()
def do_print_result(stuff):
obj = json.loads(stuff[0])
if not obj['error']:
print Fore.GREEN + '%s' % obj['result'] + Fore.RESET
else:
print Fore.RED + 'ERROR:' + '%s' % obj['error'] + Fore.RESET
def do_print_key_list(stuff):
obj = json.loads(stuff[0])
if obj['error']:
do_print_result(stuff)
return
keys = obj['result']
print Fore.GREEN
for key in keys:
print key["fingerprint"] + " " + key['address']
print Fore.RESET
def do_print_key(stuff):
obj = json.loads(stuff[0])
if obj['error']:
do_print_result(stuff)
return
key = obj['result']
print Fore.GREEN
print "Uids: " + ', '.join(key['uids'])
print "Fingerprint: " + key['fingerprint']
print "Length: " + str(key['length'])
print "Expiration: " + key['expiry_date']
print "Validation: " + key['validation']
print("Used: " + "sig:" + str(key['sign_used']) +
", encr:" + str(key['encr_used']))
print "Refresed: " + key['refreshed_at']
print Fore.RESET
print ""
print key['key_data']
def send_command(cli):
args = cli.args
subargs = cli.subargs
cb = do_print_result
cmd = args.command
if cmd == 'launch':
def launch(self, raw_args):
# XXX careful! Should see if the process in PID is running,
# avoid launching again.
import commands
commands.getoutput('bitmaskd')
reactor.stop()
return
elif cmd == 'version':
do_print_result([json.dumps(
{'result': 'bitmask_cli: 0.0.1',
'error': None})])
data = ('version',)
elif cmd == 'status':
data = ('status',)
return defer.succeed(None)
elif cmd == 'shutdown':
data = ('shutdown',)
def version(self, raw_args):
print Fore.GREEN + 'bitmask_cli: 0.0.1' + Fore.RESET
self.data = ['version']
return self._send()
elif cmd == 'debug':
data = ('stats',)
elif cmd == 'user':
if 1 != (subargs.active + subargs.create +
subargs.authenticate + subargs.logout):
error('Use bitmask_cli user --help to see available subcommands',
stop=True)
return
data = ['user']
if subargs.active:
data += ['active', '', '']
else:
if subargs.create:
data.append('signup')
elif subargs.authenticate:
data.append('authenticate')
elif subargs.logout:
data.append('logout')
username = subargs.username
if username and '@' not in username:
error("Username ID must be in the form <user@example.org>",
stop=True)
return
if not subargs.logout and not username:
error("Missing username ID but needed for this command",
stop=True)
return
elif not username:
username = ''
data.append(username)
if not subargs.logout:
passwd = getpass.getpass()
data.append(passwd)
elif cmd == 'mail':
data = ['mail']
if subargs.status:
data += ['status']
elif subargs.enable:
data += ['enable']
elif subargs.disable:
data += ['disable']
elif subargs.get_token:
data += ['get_token']
elif subargs.get_smtp_certificate:
data += ['get_smtp_certificate']
else:
error('Use bitmask_cli mail --help to see available subcommands',
stop=True)
return
elif cmd == 'eip':
data = ['eip']
if subargs.status:
data += ['status']
elif subargs.enable:
data += ['enable']
elif subargs.disable:
data += ['disable']
elif subargs.start:
data += ['start']
elif subargs.stop:
data += ['stop']
else:
error('Use bitmask_cli eip --help to see available subcommands',
stop=True)
return
elif cmd == 'keys':
data = ['keys']
if subargs.list:
data += ['list']
cb = do_print_key_list
elif subargs.export:
data += ['export', subargs.address]
cb = do_print_key
elif subargs.imprt:
with open(subargs.imprt, 'r') as keyfile:
rawkey = keyfile.read()
data += ['add', subargs.address, subargs.validation, rawkey]
cb = do_print_key
elif subargs.delete:
data += ['delete', subargs.address]
else:
error('Use bitmask_cli keys --help to see available subcommands',
stop=True)
return
if subargs.private:
data += ['private']
else:
data += ['public']
s = get_zmq_connection()
d = s.sendMsg(*data, timeout=60)
d.addCallback(cb)
d.addCallback(lambda x: reactor.stop())
d.addErrback(timeout_handler)
def execute():
cli = BitmaskCLI()
d = cli.execute(sys.argv[1:])
d.addCallback(lambda _: reactor.stop())
def main():
color_init()
cli = BitmaskCLI()
reactor.callWhenRunning(reactor.callLater, 0, send_command, cli)
reactor.callWhenRunning(reactor.callLater, 0, execute)
reactor.run()
if __name__ == "__main__":
......
# -*- coding: utf-8 -*-
# sender
# Copyright (C) 2016 LEAP
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Bitmask Command Line interface: zmq sender.
"""
import argparse
import json
import sys
from colorama import init as color_init
from colorama import Fore
from twisted.internet import defer
from txzmq import ZmqEndpoint, ZmqEndpointType
from txzmq import ZmqFactory, ZmqREQConnection
from txzmq import ZmqRequestTimeoutError
from leap.bitmask.core import ENDPOINT
def _print_result(result):
print Fore.GREEN + '%s' % result + Fore.RESET
class Command(object):
service = ''
usage = '''%s %s <subcommand>''' % tuple(sys.argv[:2])
epilog = ("Use '%s %s <subcommand> --help' to learn more "
"about each command." % tuple(sys.argv[:2]))
commands = []
def __init__(self):
color_init()
zf = ZmqFactory()
e = ZmqEndpoint(ZmqEndpointType.connect, ENDPOINT)
self._conn = ZmqREQConnection(zf, e)
self.data = []
if self.service:
self.data = [self.service]
def execute(self, raw_args):
self.parser = argparse.ArgumentParser(usage=self.usage,
epilog=self.epilog)
self.parser.add_argument('command', help='Subcommand to run')
try:
args = self.parser.parse_args(raw_args[0:1])
except SystemExit:
return defer.succeed(None)
if args.command in self.commands:
self.data += [args.command]
return self._send()
elif (args.command == 'execute' or
args.command.startswith('_') or
not hasattr(self, args.command)):
print 'Unrecognized command'
return self.help([])
try:
# use dispatch pattern to invoke method with same name
return getattr(self, args.command)(raw_args[1:])
except SystemExit:
return defer.succeed(None)
def help(self, raw_args):
self.parser.print_help()
return defer.succeed(None)
def _send(self, cb=_print_result):
d = self._conn.sendMsg(*self.data, timeout=60)
d.addCallback(self._check_err, cb)
d.addErrback(self._timeout_handler)
return d
def _error(self, msg):
print Fore.RED + "[!] %s" % msg + Fore.RESET
sys.exit(1)
def _check_err(self, stuff, cb):
obj = json.loads(stuff[0])
if not obj['error']:
return cb(obj['result'])
else:
print Fore.RED + 'ERROR:' + '%s' % obj['error'] + Fore.RESET
def _timeout_handler(self, failure):
# TODO ---- could try to launch the bitmask daemon here and retry
if failure.trap(ZmqRequestTimeoutError) == ZmqRequestTimeoutError:
print (Fore.RED + "[ERROR] Timeout contacting the bitmask daemon. "
"Is it running?" + Fore.RESET)
# -*- coding: utf-8 -*-
# eip
# Copyright (C) 2016 LEAP
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Bitmask Command Line interface: eip
"""
import sys
from leap.bitmask.cli.command import Command
class Eip(Command):
service = 'eip'
usage = '''%s eip <subcommand>
Bitmask encrypted internet service
SUBCOMMANDS:
start Start service
stop Stop service
status Display status about service
''' % sys.argv[0]
commands = ['start', 'stop', 'status']
# -*- coding: utf-8 -*-
# keys
# Copyright (C) 2016 LEAP
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Bitmask Command Line interface: keys
"""
import argparse
import sys
from colorama import Fore
from leap.bitmask.cli.command import Command
from leap.keymanager.validation import ValidationLevels
class Keys(Command):
service = 'keys'
usage = '''%s keys <subcommand>
Bitmask Keymanager management service
SUBCOMMANDS:
list List all known keys
export Export a given key
insert Insert a key to the key storage
delete Delete a key from the key storage
''' % sys.argv[0]
def list(self, raw_args):
parser = argparse.ArgumentParser(
description='Bitmask list keys',
prog='%s %s %s' % tuple(sys.argv[:3]))
parser.add_argument('--private'<