📜 ⬆️ ⬇️

FTP server with authorization through the database

image

There are many ready-made FTP servers for deployment on your server. But it turned out that FTP is already running on the server and you need to raise the FTP server on the alternate port. And also give users access only to their file folders. I decided to ask what can be done with Python tools. The search quickly issued the pyFTPd library.


In the ready-made examples of this library it is shown how to raise your FTP server in a couple of minutes. Users and the path to the files to which they should be able to get are stored in the database. Thus, it was decided to base this library and link it with the database. And get an FTP server with your own buns :)
')

Database


The table in the database is nothing extra complicated.
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 ` )
)


The main parameters that are used are the login, password, access rights and the path to the folder where the user will have access.

Implementation


The first thing that was done right away was a small class wrapper for working with a database. Thus, rewriting it to fit your needs, you can replace MySQL with any database.

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


After examining Wikipedia and the source code of the server itself, methods were identified that are responsible for authorizing and choosing the path to the location of the files. It followed that it was necessary to redefine these methods, and the problem was solved.
The server startup method is as follows:
. . .
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()
. . .


Basic methods that have been overridden



validate_authentication - is responsible for authorizing the user.
get_home_dir - get the home directory to which the user will have access.
get_perms - getting access rights to data.
has_perm - check user permissions to the directory
has_user - check for user existence

A separate class was implemented for working with users:
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


Wrapping the necessary methods:
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


Result


The user is authorized through the database and gets access to his directory.

What can be improved


To offload database calls, you can use caching. For example, memcache. There can be a great many options here, for example:


Source


Source codes can be downloaded here .

Sources


http://en.wikipedia.org/wiki/File_Transfer_Protocol
http://code.google.com/p/pyftpdlib/

Source: https://habr.com/ru/post/108796/


All Articles