Python
November 2010 25

FTP сервер с авторизацией через базу данных

image

Существует множество готовых FTP серверов для разворачивании у себя на сервере. Но сложилось так что, на сервере уже работает FTP и нужно поднять FTP сервер на альтернативном порту. А также раздать пользователям доступ только к своим папкам с файлами. Решил поинтересоваться, а что можно сделать средствами Python. Поиск быстро выдал библиотеку pyFTPd.


В готовых примерах данной библиотеки показано как за пару минут поднять свой FTP сервер. Пользователи и путь к файлам, к которым они должны иметь возможность добраться хранятся в базе. Таким образом, было принято решение взять за основу данную библиотеку и связать ее с БД. И получить FTP сервер со своими плюшками :)

База данных


Таблица в БД не представляет ничего сверх сложного.
SQL:
CREATE TABLE `users` (
  `id` int(11NOT NULL auto_increment,
  `username` varchar(255NOT NULL,
  `password` varchar(32NOT NULL,
  `path` varchar(255NOT NULL,
  `perm` varchar(8default 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/
+11
5.9k 50
Comments 56
Top of the day