diff --git a/demo/keycert.pem b/demo/keycert.pem new file mode 100644 index 00000000..bfa28def --- /dev/null +++ b/demo/keycert.pem @@ -0,0 +1,32 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXwIBAAKBgQC8ddrhm+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9L +opdJhTvbGfEj0DQs1IE8M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVH +fhi/VwovESJlaBOp+WMnfhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQAB +AoGBAK0FZpaKj6WnJZN0RqhhK+ggtBWwBnc0U/ozgKz2j1s3fsShYeiGtW6CK5nU +D1dZ5wzhbGThI7LiOXDvRucc9n7vUgi0alqPQ/PFodPxAN/eEYkmXQ7W2k7zwsDA +IUK0KUhktQbLu8qF/m8qM86ba9y9/9YkXuQbZ3COl5ahTZrhAkEA301P08RKv3KM +oXnGU2UHTuJ1MAD2hOrPxjD4/wxA/39EWG9bZczbJyggB4RHu0I3NOSFjAm3HQm0 +ANOu5QK9owJBANgOeLfNNcF4pp+UikRFqxk5hULqRAWzVxVrWe85FlPm0VVmHbb/ +loif7mqjU8o1jTd/LM7RD9f2usZyE2psaw8CQQCNLhkpX3KO5kKJmS9N7JMZSc4j +oog58yeYO8BBqKKzpug0LXuQultYv2K4veaIO04iL9VLe5z9S/Q1jaCHBBuXAkEA +z8gjGoi1AOp6PBBLZNsncCvcV/0aC+1se4HxTNo2+duKSDnbq+ljqOM+E7odU+Nq +ewvIWOG//e8fssd0mq3HywJBAJ8l/c8GVmrpFTx8r/nZ2Pyyjt3dH1widooDXYSV +q6Gbf41Llo5sYAtmxdndTLASuHKecacTgZVhy0FryZpLKrU= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICpzCCAhCgAwIBAgIJAP+qStv1cIGNMA0GCSqGSIb3DQEBBQUAMIGJMQswCQYD +VQQGEwJVUzERMA8GA1UECBMIRGVsYXdhcmUxEzARBgNVBAcTCldpbG1pbmd0b24x +IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMQwwCgYDVQQLEwNT +U0wxHzAdBgNVBAMTFnNvbWVtYWNoaW5lLnB5dGhvbi5vcmcwHhcNMDcwODI3MTY1 +NDUwWhcNMTMwMjE2MTY1NDUwWjCBiTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCERl +bGF3YXJlMRMwEQYDVQQHEwpXaWxtaW5ndG9uMSMwIQYDVQQKExpQeXRob24gU29m +dHdhcmUgRm91bmRhdGlvbjEMMAoGA1UECxMDU1NMMR8wHQYDVQQDExZzb21lbWFj +aGluZS5weXRob24ub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8ddrh +m+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9LopdJhTvbGfEj0DQs1IE8 +M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVHfhi/VwovESJlaBOp+WMn +fhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQABoxUwEzARBglghkgBhvhC +AQEEBAMCBkAwDQYJKoZIhvcNAQEFBQADgYEAF4Q5BVqmCOLv1n8je/Jw9K669VXb +08hyGzQhkemEBYQd6fzQ9A/1ZzHkJKb1P6yreOLSEh4KcxYPyrLRC1ll8nr5OlCx +CMhKkTnR6qBsdNV0XtdU2+N25hqW+Ma4ZeqsN/iiJVCGNOZGnvQuvCAGWF8+J/f/ +iHkC6gGdBJhogs4= +-----END CERTIFICATE----- diff --git a/demo/tls_ftpd.py b/demo/tls_ftpd.py new file mode 100644 index 00000000..6b2e6a48 --- /dev/null +++ b/demo/tls_ftpd.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# tls_ftpd.py + +"""RFC-2228 asynchronous FTPS server.""" + +import ssl +import os +import asyncore + +from pyftpdlib.ftpserver import * + + +CERTFILE = 'keycert.pem' + +new_proto_cmds = { + # cmd : (perm, auth, arg, path, help) + 'AUTH': (None, False, True, False, 'Syntax: AUTH TLS|SSL (set up secure control connection).'), + 'PBSZ': (None, False, True, False, 'Syntax: PBSZ 0 (negotiate size of buffer for secure data transfer).'), + 'PROT': (None, False, True, False, 'Syntax: PROT [C|P] (set up un/secure data channel).'), + } + +from pyftpdlib.ftpserver import _CommandProperty +for cmd, properties in new_proto_cmds.iteritems(): + proto_cmds[cmd] = _CommandProperty(*properties) +del cmd, properties, new_proto_cmds, _CommandProperty + + +class SSLConnection(object, asyncore.dispatcher): + + def do_ssl_handshake(self): + try: + self.socket.do_handshake() + self.ssl_accepting = False + except ssl.SSLError, err: + if err.args[0] in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): + return + raise + + def handle_read_event(self): + if self.ssl_accepting: + self.do_ssl_handshake() + else: + asyncore.dispatcher.handle_read_event(self) + + def handle_write_event(self): + if self.ssl_accepting: + self.do_ssl_handshake() + else: + asyncore.dispatcher.handle_write_event(self) + + def send(self, data): + try: + return asyncore.dispatcher.send(self, data) + except ssl.SSLError, err: + if err.args[0] == ssl.SSL_ERROR_EOF: + return 0 + raise + + def recv(self, buffer_size): + try: + return asyncore.dispatcher.recv(self, buffer_size) + except ssl.SSLError, err: + if err.args[0] == ssl.SSL_ERROR_EOF: + self.handle_close() + return '' + raise + + +class TLS_DTPHandler(SSLConnection, DTPHandler): + + do_ssl_handshake = SSLConnection.do_ssl_handshake + handle_read_event = SSLConnection.handle_read_event + handle_write_event = SSLConnection.handle_write_event + send = SSLConnection.send + recv = SSLConnection.recv + + def __init__(self, sock_obj, cmd_channel): + DTPHandler.__init__(self, sock_obj, cmd_channel) + self.ssl_accepting = False + if self.cmd_channel.secure_data_channel: + self.socket = ssl.wrap_socket(self.socket, do_handshake_on_connect=0, + certfile=CERTFILE, server_side=True, + suppress_ragged_eofs=False) + self.ssl_accepting = True + + +class TLS_FTPHandler(SSLConnection, FTPHandler): + + dtp_handler = TLS_DTPHandler + + do_ssl_handshake = SSLConnection.do_ssl_handshake + handle_read_event = SSLConnection.handle_read_event + handle_write_event = SSLConnection.handle_write_event + send = SSLConnection.send + recv = SSLConnection.recv + + def __init__(self, conn, server): + FTPHandler.__init__(self, conn, server) + self.ssl_accepting = False + self.secure_data_channel = False + + + def ftp_AUTH(self, line): + self.respond('234 AUTH TLS successful') + self.socket = ssl.wrap_socket(self.socket, suppress_ragged_eofs=False, + certfile=CERTFILE, server_side=True, + do_handshake_on_connect=False) + self.ssl_accepting = True + + def ftp_PBSZ(self, line): + self.respond('200 PBSZ=0 successful.') + + def ftp_PROT(self, line): + self.respond('200 Protection set to Private') + self.secure_data_channel = True + + +if __name__ == '__main__': + authorizer = DummyAuthorizer() + authorizer.add_user('user', '12345', os.getcwd(), perm='elradfmw') + authorizer.add_anonymous(os.getcwd()) + ftp_handler = TLS_FTPHandler + ftp_handler.authorizer = authorizer + address = ('', 21) + ftpd = FTPServer(address, ftp_handler) + ftpd.serve_forever()