blob: f50b4aaed7c5cb964393231c8e8f2b72a3e0ef33 [file] [log] [blame]
#!/usr/bin/env python
# winnt_ftpd.py
"""A ftpd using local Windows NT account database to authenticate users
(users must already exist).
It also provides a mechanism to (temporarily) impersonate the system
users every time they are going to perform filesystem operations.
"""
import os
import win32security, win32net, pywintypes, win32con
from pyftpdlib import ftpserver
def get_profile_dir(username):
"""Return the user's profile directory."""
import _winreg, win32api
sid = win32security.ConvertSidToStringSid(
win32security.LookupAccountName(None, username)[0])
try:
key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"+"\\"+sid)
except WindowsError:
raise ftpserver.AuthorizerError("No profile directory defined for %s "
"user" %username)
value = _winreg.QueryValueEx(key, "ProfileImagePath")[0]
return win32api.ExpandEnvironmentStrings(value)
class WinNtAuthorizer(ftpserver.DummyAuthorizer):
def add_user(self, username, homedir=None, **kwargs):
"""Add a "real" system user to the virtual users table.
If no homedir argument is specified the user's profile
directory will possibly be determined and used.
The keyword arguments in kwargs are the same expected by the
original add_user method: "perm", "msg_login" and "msg_quit".
"""
# get the list of all available users on the system and check
# if provided username exists
users = [entry['name'] for entry in win32net.NetUserEnum(None, 0)[0]]
if not username in users:
raise ftpserver.AuthorizerError('No such user "%s".' %username)
if not homedir:
homedir = get_profile_dir(username)
ftpserver.DummyAuthorizer.add_user(self, username, '', homedir,
**kwargs)
def add_anonymous(self, homedir=None, realuser="Guest",
password="", **kwargs):
"""Add an anonymous user to the virtual users table.
If no homedir argument is specified the realuser's profile
directory will possibly be determined and used.
realuser and password arguments are the credentials to use for
managing anonymous sessions.
The same behaviour is followed in IIS where the Guest account
is used to do so (note: it must be enabled first).
"""
users = [entry['name'] for entry in win32net.NetUserEnum(None, 0)[0]]
if not realuser in users:
raise ftpserver.AuthorizerError('No such user "%s".' %realuser)
if not homedir:
homedir = get_profile_dir(realuser)
# make sure provided credentials are valid, otherwise an exception
# will be thrown; to do so we actually impersonate the user
self.impersonate_user(realuser, password)
self.terminate_impersonation()
ftpserver.DummyAuthorizer.add_anonymous(self, homedir, **kwargs)
self.anon_user = realuser
self.anon_pwd = password
def validate_authentication(self, username, password):
if (username == "anonymous") and self.has_user('anonymous'):
username = self.anon_user
password = self.anon_pwd
try:
win32security.LogonUser(username, None, password,
win32con.LOGON32_LOGON_INTERACTIVE,
win32con.LOGON32_PROVIDER_DEFAULT)
return True
except pywintypes.error:
return False
def impersonate_user(self, username, password):
if (username == "anonymous") and self.has_user('anonymous'):
username = self.anon_user
password = self.anon_pwd
handler = win32security.LogonUser(username, None, password,
win32con.LOGON32_LOGON_INTERACTIVE,
win32con.LOGON32_PROVIDER_DEFAULT)
win32security.ImpersonateLoggedOnUser(handler)
handler.Close()
def terminate_impersonation(self):
win32security.RevertToSelf()
if __name__ == "__main__":
authorizer = WinNtAuthorizer()
# add a user (note: user must already exists)
authorizer.add_user('user', perm='elradfmw')
# add an anonymous user using Guest account to handle the anonymous
# sessions (note: Guest must be enabled first)
authorizer.add_anonymous(os.getcwd())
ftp_handler = ftpserver.FTPHandler
ftp_handler.authorizer = authorizer
address = ('', 21)
ftpd = ftpserver.FTPServer(address, ftp_handler)
ftpd.serve_forever()