1# Copyright (c) 2009-2014 Upi Tamminen <desaster@gmail.com>
2# See the COPYRIGHT file for more information
3
4"""
5This module contains ...
6"""
7
8from __future__ import absolute_import, division
9
10import time
11from configparser import NoOptionError
12
13from twisted.conch.openssh_compat import primes
14from twisted.conch.ssh import factory
15from twisted.conch.ssh import keys
16from twisted.python import log
17
18from cowrie.core.config import CowrieConfig
19from cowrie.ssh import connection
20from cowrie.ssh import keys as cowriekeys
21from cowrie.ssh import transport as shellTransport
22from cowrie.ssh.userauth import HoneyPotSSHUserAuthServer
23from cowrie.ssh_proxy import server_transport as proxyTransport
24from cowrie.ssh_proxy.userauth import ProxySSHAuthServer
25
26
27# object is added for Python 2.7 compatibility (#1198) - as is super with args
28class CowrieSSHFactory(factory.SSHFactory, object):
29    """
30    This factory creates HoneyPotSSHTransport instances
31    They listen directly to the TCP port
32    """
33
34    starttime = None
35    privateKeys = None
36    publicKeys = None
37    primes = None
38    tac = None  # gets set later
39    ourVersionString = CowrieConfig().get('ssh', 'version',
40                                          fallback='SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2')
41
42    def __init__(self, backend, pool_handler):
43        self.pool_handler = pool_handler
44        self.backend = backend
45        self.services = {
46            b'ssh-userauth': ProxySSHAuthServer if self.backend == 'proxy' else HoneyPotSSHUserAuthServer,
47            b'ssh-connection': connection.CowrieSSHConnection,
48        }
49        super(CowrieSSHFactory, self).__init__()
50
51    def logDispatch(self, *msg, **args):
52        """
53        Special delivery to the loggers to avoid scope problems
54        """
55        args['sessionno'] = 'S{0}'.format(args['sessionno'])
56        for output in self.tac.output_plugins:
57            output.logDispatch(*msg, **args)
58
59    def startFactory(self):
60        # For use by the uptime command
61        self.starttime = time.time()
62
63        # Load/create keys
64        rsaPubKeyString, rsaPrivKeyString = cowriekeys.getRSAKeys()
65        dsaPubKeyString, dsaPrivKeyString = cowriekeys.getDSAKeys()
66        self.publicKeys = {
67            b'ssh-rsa': keys.Key.fromString(data=rsaPubKeyString),
68            b'ssh-dss': keys.Key.fromString(data=dsaPubKeyString)
69        }
70        self.privateKeys = {
71            b'ssh-rsa': keys.Key.fromString(data=rsaPrivKeyString),
72            b'ssh-dss': keys.Key.fromString(data=dsaPrivKeyString)
73        }
74
75        _modulis = '/etc/ssh/moduli', '/private/etc/moduli'
76        for _moduli in _modulis:
77            try:
78                self.primes = primes.parseModuliFile(_moduli)
79                break
80            except IOError:
81                pass
82
83        # this can come from backend in the future, check HonSSH's slim client
84        self.ourVersionString = CowrieConfig().get('ssh', 'version', fallback='SSH-2.0-OpenSSH_6.0p1 Debian-4+deb7u2')
85
86        factory.SSHFactory.startFactory(self)
87        log.msg("Ready to accept SSH connections")
88
89    def stopFactory(self):
90        factory.SSHFactory.stopFactory(self)
91
92    def buildProtocol(self, addr):
93        """
94        Create an instance of the server side of the SSH protocol.
95
96        @type addr: L{twisted.internet.interfaces.IAddress} provider
97        @param addr: The address at which the server will listen.
98
99        @rtype: L{cowrie.ssh.transport.HoneyPotSSHTransport}
100        @return: The built transport.
101        """
102        if self.backend == 'proxy':
103            t = proxyTransport.FrontendSSHTransport()
104        else:
105            t = shellTransport.HoneyPotSSHTransport()
106
107        t.ourVersionString = self.ourVersionString
108        t.supportedPublicKeys = list(self.privateKeys.keys())
109
110        if not self.primes:
111            ske = t.supportedKeyExchanges[:]
112            if b'diffie-hellman-group-exchange-sha1' in ske:
113                ske.remove(b'diffie-hellman-group-exchange-sha1')
114                log.msg("No moduli, no diffie-hellman-group-exchange-sha1")
115            if b'diffie-hellman-group-exchange-sha256' in ske:
116                ske.remove(b'diffie-hellman-group-exchange-sha256')
117                log.msg("No moduli, no diffie-hellman-group-exchange-sha256")
118            t.supportedKeyExchanges = ske
119
120        try:
121            t.supportedCiphers = [i.encode('utf-8') for i in CowrieConfig().get('ssh', 'ciphers').split(',')]
122        except NoOptionError:
123            # Reorder supported ciphers to resemble current openssh more
124            t.supportedCiphers = [
125                b'aes128-ctr',
126                b'aes192-ctr',
127                b'aes256-ctr',
128                b'aes256-cbc',
129                b'aes192-cbc',
130                b'aes128-cbc',
131                b'3des-cbc',
132                b'blowfish-cbc',
133                b'cast128-cbc',
134            ]
135
136        try:
137            t.supportedMACs = [i.encode('utf-8') for i in CowrieConfig().get('ssh', 'macs').split(',')]
138        except NoOptionError:
139            # SHA1 and MD5 are considered insecure now. Use better algos
140            # like SHA-256 and SHA-384
141            t.supportedMACs = [
142                    b'hmac-sha2-512',
143                    b'hmac-sha2-384',
144                    b'hmac-sha2-256',
145                    b'hmac-sha1',
146                    b'hmac-md5'
147                ]
148
149        try:
150            t.supportedCompressions = [i.encode('utf-8') for i in CowrieConfig().get('ssh', 'compression').split(',')]
151        except NoOptionError:
152            t.supportedCompressions = [b'zlib@openssh.com', b'zlib', b'none']
153
154        # TODO: Newer versions of SSH will use ECDSA keys too as mentioned
155        # at https://tools.ietf.org/html/draft-miller-ssh-agent-02#section-4.2.2
156        #
157        # Twisted only supports below two keys
158        t.supportedPublicKeys = [b'ssh-rsa', b'ssh-dss']
159
160        t.factory = self
161
162        return t
163