Python FTP server library provides an high-level portable interface to easily write asynchronous FTP servers with Python.
Based on asyncore / asynchat frameworks pyftpdlib is actually the most
complete RFC959 FTP server implementation available for Python programming language.
Features
- High portability:
- Entirely written in pure Python, no third party modules are used, it just works on any system where select( ) or poll( ) are available.
- Abstracted file system interface is provided. It makes no difference if you're using a DOS-like or a UNIX-like filesystem.
- Extremely flexible system of "authorizers" able to interact with account and password database of different systems (Windows NT, Unix, OSx and so on...).
- High performance:
- thanks to asyncore / asynchat frameworks it permits multiplexing I/O with various client connections within a single process / thread.
- Extremely simple and highly customizable thanks to its simpler Object Oriented API.
- Compact: the entire library is distributed in a single file (FTPServer.py).
Python 2.3 or higher.
Python 2.4.3 (#1, Oct 1 2006, 18:00:19)
[GCC 4.1.1 20060928 (Red Hat 4.1.1-28)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pyftpdlib import FTPServer
>>> authorizer = FTPServer.dummy_authorizer()
>>> authorizer.add_user('user', '12345', '/home/user', perm=('r', 'w'))
>>> authorizer.add_anonymous('/home/nobody')
>>> ftp_handler = FTPServer.ftp_handler
>>> ftp_handler.authorizer = authorizer
>>> ftp_handler.msg_connect = "Hey there!"
>>> address = ("127.0.0.1", 21)
>>> ftpd = FTPServer.ftp_server(address, ftp_handler)
>>> ftpd.serve_forever()
Serving FTP on 0.0.0.0:21.
[]10.0.0.1:1089 connected.
10.0.0.1:1089 ==> 220 Ready.
10.0.0.1:1089 <== USER anonymous
10.0.0.1:1089 ==> 331 Username ok, send passowrd.
10.0.0.1:1089 <== PASS ******
10.0.0.1:1089 ==> 230 User anonymous logged in.
[anonymous]@10.0.0.1:1089 User anonymous logged in.
10.0.0.1:1089 <== CWD /
[anonymous]@10.0.0.1:1089 OK CWD "/"
10.0.0.1:1089 ==> 250 "/" is the current directory.
10.0.0.1:1089 <== TYPE A
10.0.0.1:1089 ==> 200 Type set to: ASCII.
10.0.0.1:1089 <== PASV
10.0.0.1:1089 ==> 227 Entering passive mode (10,0,0,1,4,0)
10.0.0.1:1089 <== LIST
10.0.0.1:1089 ==> 125 Data connection already open; Transfer starting.
[anonymous]@10.0.0.1:1089 OK LIST. Transfer starting.
10.0.0.1:1089 ==> 226 Transfer complete.
[anonymous]@10.0.0.1:1089 Trasfer complete. 548 bytes transmitted.
10.0.0.1:1089 <== QUIT
10.0.0.1:1089 ==> 221 Goodbye.
[anonymous]@10.0.0.1:1089 Disconnected.
Advanced usages
FTP server library provides 3 different logging streams:
- ftpd logging that notifies the most important messages for the end-user regarding the FTPd.
- line-logging that notifies commands and responses passing through the control FTP channel.
- debug-logging used for debugging messages (function/method calls, traceback outputs, low-level informational messages and so on...).
This last one is disabled by default, the first and the second one are printed to stdout through log() and linelog() functions of FTPserver library.
Let's suppose you don't want to print FTPd messages to screen but you
want to write them into a file (for example /var/log/ftpd.log) and
'line-logs' messages into a different one ('/var/log/ftpd.lines.log').
Here's how you can do that:
#!/usr/bin/env python
from pyftpdlib import FTPServer
import os
def standard_logger(msg):
f = open('/var/log/ftpd.log', 'a')
f.write(msg + '\n')
f.close()
def line_logger(msg):
f = open('/var/log/ftpd.lines.log', 'a')
f.write(msg + '\n')
f.close()
if __name__ == "__main__":
FTPServer.log = standard_logger
FTPServer.logline = line_logger
authorizer = FTPServer.dummy_authorizer()
authorizer.add_anonymous(os.getcwd())
ftp_handler = FTPServer.ftp_handler
ftp_handler.authorizer = authorizer
address = ('', 21)
ftpd = FTPServer.ftp_server(address, ftp_handler)
ftpd.serve_forever()
Storing passwords as hash digests
Using FTP server lib with the default dummy_authorizer means that password will be stored in clear-text. If you want to implement a basic encrypted account storage system you have to write your own authorizer. The example below shows how to easily store passwords as one-way hashes by using md5 algorithm and by sub-classing the original dummy_authorizer class overriding its validate_authentication method:
#!/usr/bin/env python
# md5_ftpd.py
# FTPd storing passwords as hash digest (platform independent).
import md5
import os
from pyftpdlib import FTPServer
class dummy_encrypted_authorizer(FTPServer.dummy_authorizer):
def __init__(self):
FTPServer.dummy_authorizer.__init__(self)
def validate_authentication(self, username, password):
if username == 'anonymous':
if self.has_user('anonymous'):
return 1
else:
return 0
hash = md5.new(password).hexdigest()
return self.user_table[username]['pwd'] == hash
if __name__ == "__main__":
# get an hash digest from a clear-text password
hash = md5.new('12345').hexdigest()
authorizer = dummy_encrypted_authorizer()
authorizer.add_user('user', hash, os.getcwd(), perm=('r', 'w'))
authorizer.add_anonymous(os.getcwd())
ftp_handler = FTPServer.ftp_handler
ftp_handler.authorizer = authorizer
address = ('', 21)
ftpd = FTPServer.ftp_server(address, ftp_handler)
ftpd.serve_forever()
If you're running a Unix system you could want to configure
pyftpd to include support for 'real' users existing on the system. The
example below shows how to use pwd / spwd modules
(available since Python 2.5) to interact with UNIX user account and
password database (users must be created previously).
This basic authorizer also gets the user's home directory.
#!/usr/bin/env python
# unix_ftpd.py
# FTPd using local unix account database to authenticate users and get
# their home directories (users must be created previously). import os
import pwd, spwd, crypt
from pyftpdlib import FTPServer
class unix_authorizer(FTPServer.dummy_authorizer):
def __init__(self):
FTPServer.dummy_authorizer.__init__(self)
def add_user(self, username, home='', perm=('r')):
assert username in [i[0] for i in pwd.getpwall()], 'No such user "%s".' %username
pw = spwd.getspnam(username).sp_pwd
if not home:
home = pwd.getpwnam(username).pw_dir
assert os.path.isdir(home), 'No such directory "%s".' %home
dic = {'pwd' : pw,
'home' : home,
'perm' : perm
}
self.user_table[username] = dic
def validate_authentication(self, username, password):
if username == 'anonymous':
if self.has_user('anonymous'):
return 1
else:
return 0
else:
pw1 = spwd.getspnam(username).sp_pwd
pw2 = crypt.crypt(password, pw1)
return pw1 == pw2
if __name__ == "__main__":
authorizer = unix_authorizer()
authorizer.add_user('user', perm=('r', 'w'))
authorizer.add_anonymous(os.getcwd())
ftp_handler = FTPServer.ftp_handler
ftp_handler.authorizer = authorizer
address = ('', 21)
ftpd = FTPServer.ftp_server(address, ftp_handler)
ftpd.serve_forever()
Windows NT FTP Server
This next code shows how to implement a basic authorizer for a Windows
NT workstation (windows NT, 2000, XP, 2003 server and so on...) by
using pywin32 extension.
# winFTPserver.py
# Basic authorizer for Windows NT accounts (users must be created previously).
import os
import win32security, win32net, pywintypes
from pyftpdlib import FTPServer
class winNT_authorizer(FTPServer.dummy_authorizer):
def __init__(self):
FTPServer.dummy_authorizer.__init__(self)
def add_user(self, username, home, perm=('r')):
# check if user exists
users = [elem['name'] for elem in win32net.NetUserEnum(None, 0)[0]]
assert username in users, 'No such user "%s".' %username
assert os.path.isdir(home), 'No such directory "%s".' %home
dic = {'pwd' : None,
'home' : home,
'perm' : perm
}
self.user_table[username] = dic
def validate_authentication(self, username, password):
if username == 'anonymous':
if self.has_user('anonymous'):
return 1
else:
return 0
else:
try:
# check credentials
win32security.LogonUser (
username,
None,
password,
win32security.LOGON32_LOGON_NETWORK,
win32security.LOGON32_PROVIDER_DEFAULT
)
return 1
except pywintypes.error, err:
return 0
if __name__ == "__main__":
authorizer = winNT_authorizer()
authorizer.add_user ('user', os.getcwd(),perm=('r', 'w'))
authorizer.add_anonymous (os.getcwd())
ftp_handler = FTPServer.ftp_handler
ftp_handler.authorizer = authorizer
address = ('', 21)
ftpd = FTPServer.ftp_server(address, ftp_handler)
ftpd.serve_forever()
EOF
billiejoex 2007-02-20 05:28PM