summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKali Kaneko (leap communications) <kali@leap.se>2017-04-27 19:13:22 +0200
committerKali Kaneko (leap communications) <kali@leap.se>2017-05-01 16:13:13 +0200
commit5009f2c227ab55d70022f24f7f32299e26fd11ea (patch)
tree4a9f0de06387452c1e4f487dcc4d11044a5a76c7 /src
parentad9a4a2326fd6f596072981f21c64a6d984bd6cb (diff)
[feature] streamline and move manhole into core
Diffstat (limited to 'src')
-rw-r--r--src/leap/bitmask/core/manhole.py143
-rw-r--r--src/leap/bitmask/core/service.py32
-rw-r--r--src/leap/bitmask/mail/imap/service/__init__.py14
-rw-r--r--src/leap/bitmask/mail/imap/service/manhole.py130
4 files changed, 174 insertions, 145 deletions
diff --git a/src/leap/bitmask/core/manhole.py b/src/leap/bitmask/core/manhole.py
new file mode 100644
index 0000000..1a1b10d
--- /dev/null
+++ b/src/leap/bitmask/core/manhole.py
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+# manhole.py
+# Copyright (C) 2014-2017 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/>.
+
+"""
+Utilities for enabling the manhole administrative interface into Bitmask Core.
+"""
+
+PORT = 2222
+
+
+def getManholeFactory(namespace, user, secret, keydir=None):
+ """
+ Get an administrative manhole into the application.
+
+ :param namespace: the namespace to show in the manhole
+ :type namespace: dict
+ :param user: the user to authenticate into the administrative shell.
+ :type user: str
+ :param secret: pass for this manhole
+ :type secret: str
+ """
+ import string
+
+ from twisted.cred import portal
+ from twisted.conch import manhole, manhole_ssh
+ from twisted.conch import recvline
+ from twisted.conch.insults import insults
+ from twisted.conch.ssh import keys
+ from twisted.cred.checkers import (
+ InMemoryUsernamePasswordDatabaseDontUse as MemoryDB)
+ from twisted.python import filepath
+
+ try:
+ from IPython.core.completer import Completer
+ except ImportError:
+ from rlcompleter import Completer
+
+ class EnhancedColoredManhole(manhole.ColoredManhole):
+ """
+ A nicer Manhole with some autocomplete support.
+
+ See the patch in https://twistedmatrix.com/trac/ticket/6863
+ Since you're reading this, it'd be good if *you* can help getting that
+ patch into twisted :)
+ """
+
+ completion = True
+
+ def handle_TAB(self):
+ """
+ If tab completion is available and enabled then perform some tab
+ completion.
+ """
+ if not self.completion:
+ recvline.HistoricRecvLine.handle_TAB(self)
+ return
+ # If we only have whitespace characters on this line we pass
+ # through the tab
+ if set(self.lineBuffer).issubset(string.whitespace):
+ recvline.HistoricRecvLine.handle_TAB(self)
+ return
+ cp = Completer(namespace=self.namespace)
+ cp.limit_to__all__ = False
+ lineLeft, lineRight = self.currentLineBuffer()
+
+ # Extract all the matches
+ matches = []
+ n = 0
+ while True:
+ match = cp.complete(lineLeft, n)
+ if match is None:
+ break
+ n += 1
+ matches.append(match)
+
+ if not matches:
+ return
+
+ if len(matches) == 1:
+ # Found the match so replace the line. This is apparently how
+ # we replace a line
+ self.handle_HOME()
+ self.terminal.eraseToLineEnd()
+
+ self.lineBuffer = []
+ self._deliverBuffer(matches[0] + lineRight)
+ else:
+ # Must have more than one match, display them
+ matches.sort()
+ self.terminal.write("\n")
+ self.terminal.write(" ".join(matches))
+ self.terminal.write("\n\n")
+ self.drawInputLine()
+
+ def keystrokeReceived(self, keyID, modifier):
+ """
+ Act upon any keystroke received.
+ """
+ self.keyHandlers.update({'\b': self.handle_BACKSPACE})
+ m = self.keyHandlers.get(keyID)
+ if m is not None:
+ m()
+ elif keyID in string.printable:
+ self.characterReceived(keyID, False)
+
+ class chainedProtocolFactory:
+ def __init__(self, namespace):
+ self.namespace = namespace
+
+ def __call__(self):
+ return insults.ServerProtocol(
+ EnhancedColoredManhole, self.namespace)
+
+ sshRealm = manhole_ssh.TerminalRealm()
+ sshRealm.chainedProtocolFactory = chainedProtocolFactory(namespace)
+
+ checker = MemoryDB(**{user: secret})
+ sshPortal = portal.Portal(sshRealm, [checker])
+ sshFactory = manhole_ssh.ConchFactory(sshPortal)
+
+ if not keydir:
+ from twisted.python._appdirs import getDataDirectory
+ keydir = getDataDirectory()
+
+ keyLocation = filepath.FilePath(keydir).child('id_rsa')
+ sshKey = keys._getPersistentRSAKey(keyLocation, 4096)
+ sshFactory.publicKeys[b"ssh-rsa"] = sshKey
+ sshFactory.privateKeys[b"ssh-rsa"] = sshKey
+ return sshFactory
diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py
index 2972a51..314c889 100644
--- a/src/leap/bitmask/core/service.py
+++ b/src/leap/bitmask/core/service.py
@@ -20,18 +20,22 @@ Bitmask-core Service.
import json
import os
import uuid
+import tempfile
try:
import resource
except ImportError:
pass
+from twisted.conch import manhole_tap
from twisted.internet import reactor
+from twisted.internet.endpoints import TCP4ServerEndpoint
from twisted.logger import Logger
from leap.bitmask import __version__
from leap.bitmask.core import configurable
-from leap.bitmask.core import _zmq
+from leap.bitmask.core import manhole
from leap.bitmask.core import flags
+from leap.bitmask.core import _zmq
from leap.bitmask.core import _session
from leap.bitmask.core.web.service import HTTPDispatcherService
from leap.bitmask.vpn.service import VPNService
@@ -80,6 +84,15 @@ class BitmaskBackend(configurable.ConfigurableService):
def enabled(service):
return self.get_config('services', service, False, boolean=True)
+ def with_manhole():
+ user = self.get_config('manhole', 'user', None)
+ passwd = self.get_config('manhole', 'passwd', None)
+ port = self.get_config('manhole', 'port', None)
+ if user and passwd:
+ conf = {'user': user, 'passwd': passwd, 'port': port}
+ return conf
+ return None
+
on_start = reactor.callWhenRunning
on_start(self.init_events)
@@ -102,6 +115,10 @@ class BitmaskBackend(configurable.ConfigurableService):
if enabled('websockets'):
on_start(self._init_websockets)
+ manholecfg = with_manhole()
+ if manhole:
+ on_start(self._init_manhole, manholecfg)
+
def _touch_token_file(self):
path = os.path.join(self.basedir, 'authtoken')
with open(path, 'w') as f:
@@ -209,6 +226,19 @@ class BitmaskBackend(configurable.ConfigurableService):
service.setServiceParent(self)
return service
+ def _init_manhole(self, cfg):
+ try:
+ port = int(cfg.get('port'))
+ except ValueError:
+ port = manhole.PORT
+ user, passwd = cfg['user'], cfg['passwd']
+ sshFactory = manhole.getManholeFactory(
+ {'core': self}, user, passwd)
+ endpoint = TCP4ServerEndpoint(reactor, port)
+ endpoint.listen(sshFactory)
+
+ log.info('Started manhole in PORT {0!s}'.format(port))
+
def do_stats(self):
return self.core_commands.do_stats()
diff --git a/src/leap/bitmask/mail/imap/service/__init__.py b/src/leap/bitmask/mail/imap/service/__init__.py
index b3673c2..712a2d6 100644
--- a/src/leap/bitmask/mail/imap/service/__init__.py
+++ b/src/leap/bitmask/mail/imap/service/__init__.py
@@ -39,12 +39,8 @@ from leap.bitmask.mail.imap.server import LEAPIMAPServer
log = Logger()
-DO_MANHOLE = os.environ.get("LEAP_MAIL_MANHOLE", None)
-if DO_MANHOLE:
- from leap.bitmask.mail.imap.service import manhole
# The default port in which imap service will run
-
IMAP_PORT = 1984
#
@@ -180,16 +176,6 @@ def run_service(soledad_sessions, port=IMAP_PORT, factory=None):
log.error("Error launching IMAP service: %r" % (exc,))
else:
# all good.
-
- if DO_MANHOLE:
- # TODO get pass from env var.too.
- manhole_factory = manhole.getManholeFactory(
- {'f': factory,
- 'gm': factory.theAccount.getMailbox},
- "boss", "leap")
- # TODO use Endpoints !!!
- reactor.listenTCP(manhole.MANHOLE_PORT, manhole_factory,
- interface="127.0.0.1")
log.debug('IMAP4 Server is RUNNING in port %s' % (port,))
emit_async(catalog.IMAP_SERVICE_STARTED, str(port))
diff --git a/src/leap/bitmask/mail/imap/service/manhole.py b/src/leap/bitmask/mail/imap/service/manhole.py
deleted file mode 100644
index c83ae89..0000000
--- a/src/leap/bitmask/mail/imap/service/manhole.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# -*- coding: utf-8 -*-
-# manhole.py
-# Copyright (C) 2014 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/>.
-"""
-Utilities for enabling the manhole administrative interface into the
-LEAP Mail application.
-"""
-MANHOLE_PORT = 2222
-
-
-def getManholeFactory(namespace, user, secret):
- """
- Get an administrative manhole into the application.
-
- :param namespace: the namespace to show in the manhole
- :type namespace: dict
- :param user: the user to authenticate into the administrative shell.
- :type user: str
- :param secret: pass for this manhole
- :type secret: str
- """
- import string
-
- from twisted.cred.portal import Portal
- from twisted.conch import manhole, manhole_ssh
- from twisted.conch.insults import insults
- from twisted.cred.checkers import (
- InMemoryUsernamePasswordDatabaseDontUse as MemoryDB)
-
- from rlcompleter import Completer
-
- class EnhancedColoredManhole(manhole.ColoredManhole):
- """
- A Manhole with some primitive autocomplete support.
- """
- # TODO use introspection to make life easier
-
- def find_common(self, l):
- """
- find common parts in thelist items
- ex: 'ab' for ['abcd','abce','abf']
- requires an ordered list
- """
- if len(l) == 1:
- return l[0]
-
- init = l[0]
- for item in l[1:]:
- for i, (x, y) in enumerate(zip(init, item)):
- if x != y:
- init = "".join(init[:i])
- break
-
- if not init:
- return None
- return init
-
- def handle_TAB(self):
- """
- Trap the TAB keystroke.
- """
- necessarypart = "".join(self.lineBuffer).split(' ')[-1]
- completer = Completer(globals())
- if completer.complete(necessarypart, 0):
- matches = list(set(completer.matches)) # has multiples
-
- if len(matches) == 1:
- length = len(necessarypart)
- self.lineBuffer = self.lineBuffer[:-length]
- self.lineBuffer.extend(matches[0])
- self.lineBufferIndex = len(self.lineBuffer)
- else:
- matches.sort()
- commons = self.find_common(matches)
- if commons:
- length = len(necessarypart)
- self.lineBuffer = self.lineBuffer[:-length]
- self.lineBuffer.extend(commons)
- self.lineBufferIndex = len(self.lineBuffer)
-
- self.terminal.nextLine()
- while matches:
- matches, part = matches[4:], matches[:4]
- for item in part:
- self.terminal.write('%s' % item.ljust(30))
- self.terminal.write('\n')
- self.terminal.nextLine()
-
- self.terminal.eraseLine()
- self.terminal.cursorBackward(self.lineBufferIndex + 5)
- self.terminal.write("%s %s" % (
- self.ps[self.pn], "".join(self.lineBuffer)))
-
- def keystrokeReceived(self, keyID, modifier):
- """
- Act upon any keystroke received.
- """
- self.keyHandlers.update({'\b': self.handle_BACKSPACE})
- m = self.keyHandlers.get(keyID)
- if m is not None:
- m()
- elif keyID in string.printable:
- self.characterReceived(keyID, False)
-
- sshRealm = manhole_ssh.TerminalRealm()
-
- def chainedProtocolFactory():
- return insults.ServerProtocol(EnhancedColoredManhole, namespace)
-
- sshRealm = manhole_ssh.TerminalRealm()
- sshRealm.chainedProtocolFactory = chainedProtocolFactory
-
- portal = Portal(
- sshRealm, [MemoryDB(**{user: secret})])
-
- f = manhole_ssh.ConchFactory(portal)
- return f