aboutsummaryrefslogtreecommitdiff
blob: 5969b195e75facad2e43d2c15f6e71c418f18251 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# vim:fileencoding=utf8:et:ts=4:sts=4:sw=4:ft=python

from django.conf import settings

import paramiko

from io import BytesIO

import asyncore
import inspect
import socket
import threading


LISTEN_BACKLOG = 20


def ssh_handler(f):
    if not hasattr(settings, 'SSH_HANDLERS'):
        settings.SSH_HANDLERS = {}
    settings.SSH_HANDLERS[f.__name__] = f
    return f


class SSHServer(paramiko.ServerInterface):
    def __init__(self):
        paramiko.ServerInterface.__init__(self)
        self._message = None

    def get_allowed_auths(self, username):
        return 'publickey'

    def check_auth_publickey(self, username, key):
        # for some reason, this is called twice...
        if self._message:
            return paramiko.AUTH_SUCCESSFUL

        spl = username.split('+')
        cmd = spl[0]
        args = spl[1:]

        try:
            h = settings.SSH_HANDLERS[cmd]
            # this is an easy way of checking if we have correct args
            inspect.getcallargs(h, *args, key=key)
        except (KeyError, TypeError) as e:
            pass
        else:
            ret = h(*args, key=key)
            if ret is not None:
                self._message = ret
                return paramiko.AUTH_SUCCESSFUL
        return paramiko.AUTH_FAILED

    def check_channel_request(self, kind, chanid):
        if kind == 'session':
            return paramiko.OPEN_SUCCEEDED
        return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED

    def check_channel_subsystem_request(self, channel, name):
        return False

    def check_channel_exec_request(self, channel, command):
        channel.send('%s\r\n' % self._message)
        channel.shutdown(2)
        channel.close()
        self._message = None
        return True

    def check_channel_shell_request(self, channel):
        channel.send('%s\r\n' % self._message)
        channel.shutdown(2)
        channel.close()
        self._message = None
        return True

    def check_channel_pty_request(self, channel, term, width, height,
            pixelwidth, pixelheight, modes):
        return True


class SSHDispatcher(asyncore.dispatcher):
    def __init__(self, server_key):
        asyncore.dispatcher.__init__(self)
        self._server_key = server_key

        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind(settings.SSH_BIND)
        self.listen(LISTEN_BACKLOG)

    def handle_accepted(self, conn, addr):
        t = paramiko.Transport(conn)
        t.add_server_key(self._server_key)
        # we need a dummy Event to make it non-blocking
        # but we don't really need to play with it
        t.start_server(event=threading.Event(), server=SSHServer())

    # python<3.2 compat
    def handle_accept(self):
        ret = self.accept()
        if ret is not None:
            self.handle_accepted(*ret)


def ssh_main():
    server_key = paramiko.RSAKey(file_obj=BytesIO(settings.SSH_SERVER_KEY))

    disp = SSHDispatcher(server_key)
    asyncore.loop()
    raise SystemError('SSH server loop exited')