
Существует множество готовых FTP серверов для разворачивании у себя на сервере. Но сложилось так что, на сервере уже работает FTP и нужно поднять FTP сервер на альтернативном порту. А также раздать пользователям доступ только к своим папкам с файлами. Решил поинтересоваться, а что можно сделать средствами Python. Поиск быстро выдал библиотеку pyFTPd.
В готовых примерах данной библиотеки показано как за пару минут поднять свой FTP сервер. Пользователи и путь к файлам, к которым они должны иметь возможность добраться хранятся в базе. Таким образом, было принято решение взять за основу данную библиотеку и связать ее с БД. И получить FTP сервер со своими плюшками :)
База данных
Таблица в БД не представляет ничего сверх сложного.
SQL:
CREATE TABLE `users` (
`id` int(11) NOT NULL auto_increment,
`username` varchar(255) NOT NULL,
`password` varchar(32) NOT NULL,
`path` varchar(255) NOT NULL,
`perm` varchar(8) default NULL,
PRIMARY KEY (`id`),
KEY `username` (`username`)
)
Основные параметры, которые используются — это логин, пароль, права доступа и путь к папке, куда пользователь будет иметь доступ.
Реализация
Первое, что было сразу сделано – небольшой класс обертка на работу с БД. Таким образом, переписывая его под свои нужды можно заменить MySQL на любую БД.
class DB:
init=None
db=None
def __init__(self,init_db):
""" Constructor """
self.init = init_db
self.db = self.init()
def doSql(self,sql):
""" Handle SQL """
try:
self.db.execute(sql)
except:
try:
self.db = self.init()
self.db.execute(sql)
except:
print "error:"+sql
def getDB(self):
""" """
return self.db
def getLastId(self):
"""Get last insert ID"""
sql = "select LAST_INSERT_ID() as `id`"
self.doSql(sql)
data = self.db.fetchone()
if 'id' in data:
return data['id']
else:
return None
Изучив википедию и исходные коды самого сервера были выявлены методы, которые отвечают за авторизацию и выбор пути расположения файлов. Отсюда следовало, что необходимо переопределить эти методы, и задача решена.
Метод запуска сервера выглядит следующим образом:
. . .
def starterver(self):
"""Run server"""
authorizer = self.ftpserver.DummyAuthorizer()
authorizer.validate_authentication = self.my_validate_authentication
authorizer.get_home_dir = self.my_get_home_dir
authorizer.get_perms = self.my_get_perms
authorizer.get_msg_login = self.my_get_msg_login
authorizer.get_msg_quit = self.my_get_msg_quit
authorizer.has_perm = self.my_has_perms
authorizer.has_user = self.my_has_user
# Instantiate FTP handler class
ftp_handler = ftpserver.FTPHandler
ftp_handler.authorizer = authorizer
ftp_handler.passive_ports = range(63000,63500)
# Define a customized banner (string returned when client connects)
ftp_handler.banner = "pyftpdlib %s based ftpd ready." %ftpserver.__ver__
address = ('127.0.0.1', 23)
ftpd = ftpserver.FTPServer(address, ftp_handler)
# set a limit for connections
ftpd.max_cons = 256
ftpd.max_cons_per_ip = 5
# start ftp server
ftpd.serve_forever()
. . .
Основные методы, которые были переопределены
validate_authentication – отвечает за авторизацию пользователя.
get_home_dir – получение домашней директории, к которой пользователь будет иметь доступ.
get_perms – получения прав доступа к данным.
has_perm – проверка прав доступа пользователя к директории
has_user – проверка на существование пользователя
Для работы с пользователям был реализован отдельный класс:
from db import DB
from config import init_db
class User:
def __init__(self):
"""Init"""
def auth(self,username,password):
"""Make auth"""
sql = "select * from `users` where `username`='%s' and `password`='%s'" % (username,password)
db = DB(init_db)
db.doSql(sql)
res = db.getDB().fetchone()
if res:
return 1
else:
return None
def getPath(self,username):
"""Return path by username"""
sql = "select `path` from `users` where `username`='%s'" % username
db = DB(init_db)
db.doSql(sql)
uparam = db.getDB().fetchone()
if uparam:
return uparam['path']
else:
return None
def getPerm(self,username):
"""Return permission by username"""
sql = "select `perm` from `users` where `username`='%s'" % username
db = DB(init_db)
db.doSql(sql)
uparam = db.getDB().fetchone()
if uparam:
return uparam['perm']
else:
return ''
def hasUser(self,username):
"""Checj user into DB"""
sql = "select `id` from `users` where `username`='%s'" % (username)
db = DB(init_db)
db.doSql(sql)
uparam = db.getDB().fetchone()
if uparam:
return 1
else:
return 0
Оборачиваем необходимые методы:
def my_validate_authentication(self,username,password):
return User().auth(username, password)
def my_get_home_dir(self,username):
return User().getPath(username)
def my_get_perms(self,username):
return User().getPerm(username)
def my_get_msg_login(self,username):
return 'hello msg login'
def my_get_msg_quit(self,username):
return 'byu msg quit'
def my_has_user(self,username):
return User().hasUser(username)
def my_has_perms(self,username, perm, path=None):
return 1
Результат
Пользователь авторизируется через БД и получает доступ к своей директории.
Что можно улучшить
Для разгрузки обращений к БД можно использовать кеширование. Например, memcache. Вариантов тут может быть великое множество, например:
- При логине пользователя записывать всю информацию о пользователе в кеш и потом читать ее оттуда
- Хранить базу данных о пользователях в кеше и периодически обновлять ее
Исходный код
Исходные коды можно скачать тут.
Источники
http://en.wikipedia.org/wiki/File_Transfer_Protocol
http://code.google.com/p/pyftpdlib/