refact: smf-cleanup
This commit is contained in:
parent
46e7837346
commit
c9c54ac81a
@ -1,14 +1,3 @@
|
||||
;Database configuration
|
||||
[database]
|
||||
;Only tested with MySQL at the moment
|
||||
lib = MySQLdb
|
||||
name = smf
|
||||
user = smf
|
||||
password = secret
|
||||
prefix = smf_
|
||||
host = 127.0.0.1
|
||||
port = 3306
|
||||
|
||||
;Django configuration
|
||||
[django]
|
||||
;If true, the authenticator will use Django to handle user authentication instead of the database
|
||||
|
||||
457
CVoipAuth.py
457
CVoipAuth.py
@ -1,41 +1,23 @@
|
||||
import sys
|
||||
import Ice
|
||||
import _thread
|
||||
import urllib.request, urllib.error, urllib.parse
|
||||
import logging
|
||||
import configparser
|
||||
import bcrypt
|
||||
|
||||
from threading import Timer
|
||||
from optparse import OptionParser
|
||||
from logging import (debug,
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
critical,
|
||||
exception,
|
||||
getLogger)
|
||||
|
||||
from hashlib import sha1
|
||||
from threading import Timer
|
||||
from optparse import OptionParser
|
||||
from logging import debug, info, warning, error, critical, exception, getLogger
|
||||
|
||||
# === Configuration Helpers ===
|
||||
def x2bool(s):
|
||||
"""Helper function to convert strings from the config to bool"""
|
||||
"""Convert config strings to boolean"""
|
||||
if isinstance(s, bool):
|
||||
return s
|
||||
elif isinstance(s, str):
|
||||
return s.lower() in ['1', 'true']
|
||||
raise ValueError()
|
||||
|
||||
#
|
||||
#--- Default configuration values
|
||||
#
|
||||
# === Default Configuration ===
|
||||
cfgfile = 'CVoipAuth.ini'
|
||||
default = {'database':(('lib', str, 'MySQLdb'),
|
||||
('password', str, 'secret'),
|
||||
('host', str, '127.0.0.1'),
|
||||
('port', int, 3306)),
|
||||
|
||||
'django':(('enabled', x2bool, False),
|
||||
default = {'django':(('enabled', x2bool, False),
|
||||
('project', str, 'CVoipPanel'),
|
||||
('settings', str, 'CVoipPanel.settings')),
|
||||
|
||||
@ -61,35 +43,29 @@ default = {'database':(('lib', str, 'MySQLdb'),
|
||||
'log':(('level', int, logging.DEBUG),
|
||||
('file', str, 'CVoipAuth.log'))}
|
||||
|
||||
#
|
||||
#--- Helper classes
|
||||
#
|
||||
# === Helper classes ===
|
||||
class config(object):
|
||||
"""
|
||||
Small abstraction for config loading
|
||||
"""
|
||||
|
||||
def __init__(self, filename = None, default = None):
|
||||
if not filename or not default: return
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.optionxform = str
|
||||
cfg.read(filename)
|
||||
|
||||
for h,v in default.items():
|
||||
if not v:
|
||||
# Output this whole section as a list of raw key/value tuples
|
||||
for section, values in default.items():
|
||||
if not values:
|
||||
try:
|
||||
self.__dict__[h] = cfg.items(h)
|
||||
self.__dict__[section] = cfg.items(section)
|
||||
except configparser.NoSectionError:
|
||||
self.__dict__[h] = []
|
||||
self.__dict__[section] = []
|
||||
else:
|
||||
self.__dict__[h] = config()
|
||||
for name, conv, vdefault in v:
|
||||
self.__dict__[section] = config()
|
||||
for name, conv, vdefault in values:
|
||||
try:
|
||||
self.__dict__[h].__dict__[name] = conv(cfg.get(h, name))
|
||||
val = cfg.get(section, name)
|
||||
self.__dict__[section].__dict__[name] = conv(val)
|
||||
except (ValueError, configparser.NoSectionError, configparser.NoOptionError):
|
||||
self.__dict__[h].__dict__[name] = vdefault
|
||||
|
||||
self.__dict__[section].__dict__[name] = vdefault
|
||||
|
||||
# === HTML Entity Handling ===
|
||||
def entity_decode(string):
|
||||
"""
|
||||
Python reverse implementation of php htmlspecialchars
|
||||
@ -117,88 +93,8 @@ def entity_encode(string):
|
||||
for (s,t) in htmlspecialchars:
|
||||
ret = ret.replace(s, t)
|
||||
return ret
|
||||
|
||||
class threadDbException(Exception): pass
|
||||
class threadDB(object):
|
||||
"""
|
||||
Small abstraction to handle database connections for multiple
|
||||
threads
|
||||
"""
|
||||
|
||||
db_connections = {}
|
||||
|
||||
def connection(cls):
|
||||
tid = _thread.get_ident()
|
||||
try:
|
||||
con = cls.db_connections[tid]
|
||||
except:
|
||||
info('Connecting to database server (%s %s:%d %s) for thread %d',
|
||||
cfg.database.lib, cfg.database.host, cfg.database.port, cfg.database.name, tid)
|
||||
|
||||
try:
|
||||
con = db.connect(host = cfg.database.host,
|
||||
port = cfg.database.port,
|
||||
user = cfg.database.user,
|
||||
passwd = cfg.database.password,
|
||||
db = cfg.database.name,
|
||||
charset = 'utf8')
|
||||
# Transactional engines like InnoDB initiate a transaction even
|
||||
# on SELECTs-only. Thus, we auto-commit so smfauth gets recent data.
|
||||
con.autocommit(True)
|
||||
except db.Error as e:
|
||||
error('Could not connect to database: %s', str(e))
|
||||
raise threadDbException()
|
||||
cls.db_connections[tid] = con
|
||||
return con
|
||||
connection = classmethod(connection)
|
||||
|
||||
def cursor(cls):
|
||||
return cls.connection().cursor()
|
||||
cursor = classmethod(cursor)
|
||||
|
||||
def execute(cls, *args, **kwargs):
|
||||
if "threadDB__retry_execution__" in kwargs:
|
||||
# Have a magic keyword so we can call ourselves while preventing
|
||||
# an infinite loop
|
||||
del kwargs["threadDB__retry_execution__"]
|
||||
retry = False
|
||||
else:
|
||||
retry = True
|
||||
|
||||
c = cls.cursor()
|
||||
try:
|
||||
c.execute(*args, **kwargs)
|
||||
except db.OperationalError as e:
|
||||
error('Database operational error %d: %s', e.args[0], e.args[1])
|
||||
c.close()
|
||||
cls.invalidate_connection()
|
||||
if retry:
|
||||
# Make sure we only retry once
|
||||
info('Retrying database operation')
|
||||
kwargs["threadDB__retry_execution__"] = True
|
||||
c = cls.execute(*args, **kwargs)
|
||||
else:
|
||||
error('Database operation failed ultimately')
|
||||
raise threadDbException()
|
||||
return c
|
||||
execute = classmethod(execute)
|
||||
|
||||
def invalidate_connection(cls):
|
||||
tid = _thread.get_ident()
|
||||
con = cls.db_connections.pop(tid, None)
|
||||
if con:
|
||||
debug('Invalidate connection to database for thread %d', tid)
|
||||
con.close()
|
||||
|
||||
invalidate_connection = classmethod(invalidate_connection)
|
||||
|
||||
def disconnect(cls):
|
||||
while cls.db_connections:
|
||||
tid, con = cls.db_connections.popitem()
|
||||
debug('Close database connection for thread %d', tid)
|
||||
con.close()
|
||||
disconnect = classmethod(disconnect)
|
||||
|
||||
# === Main Application ===
|
||||
def do_main_program():
|
||||
#
|
||||
#--- Authenticator implementation
|
||||
@ -222,101 +118,60 @@ def do_main_program():
|
||||
if cfg.ice.watchdog > 0:
|
||||
self.failedWatch = True
|
||||
self.checkConnection()
|
||||
|
||||
# Serve till we are stopped
|
||||
self.communicator().waitForShutdown()
|
||||
self.watchdog.cancel()
|
||||
|
||||
|
||||
if hasattr(self, 'watchdog'):
|
||||
self.watchdog.cancel()
|
||||
if self.interrupted():
|
||||
warning('Caught interrupt, shutting down')
|
||||
|
||||
threadDB.disconnect()
|
||||
warning('Interrupt received - shutting down')
|
||||
return 0
|
||||
|
||||
|
||||
def initializeIceConnection(self):
|
||||
"""
|
||||
Establishes the two-way Ice connection and adds the authenticator to the
|
||||
configured servers
|
||||
"""
|
||||
ice = self.communicator()
|
||||
|
||||
if cfg.ice.secret:
|
||||
debug('Using shared ice secret')
|
||||
ice.getImplicitContext().put("secret", cfg.ice.secret)
|
||||
elif not cfg.glacier.enabled:
|
||||
warning('Consider using an ice secret to improve security')
|
||||
warning('No Ice secret configured - security risk')
|
||||
|
||||
if cfg.glacier.enabled:
|
||||
#info('Connecting to Glacier2 server (%s:%d)', glacier_host, glacier_port)
|
||||
error('Glacier support not implemented yet')
|
||||
#TODO: Implement this
|
||||
|
||||
info('Connecting to Ice server (%s:%d)', cfg.ice.host, cfg.ice.port)
|
||||
base = ice.stringToProxy('Meta:tcp -h %s -p %d' % (cfg.ice.host, cfg.ice.port))
|
||||
self.meta = MumbleServer.MetaPrx.uncheckedCast(base)
|
||||
|
||||
adapter = ice.createObjectAdapterWithEndpoints('Callback.Client', 'tcp -h %s' % cfg.ice.host)
|
||||
info('Connecting to Ice at %s:%d', cfg.ice.host, cfg.ice.port)
|
||||
proxy = ice.stringToProxy(f'Meta:tcp -h {cfg.ice.host} -p {cfg.ice.port}')
|
||||
self.meta = MumbleServer.MetaPrx.uncheckedCast(proxy)
|
||||
|
||||
adapter = ice.createObjectAdapterWithEndpoints('Callback.Client', f'tcp -h {cfg.ice.host}')
|
||||
adapter.activate()
|
||||
|
||||
metacbprx = adapter.addWithUUID(metaCallback(self))
|
||||
self.metacb = MumbleServer.MetaCallbackPrx.uncheckedCast(metacbprx)
|
||||
|
||||
authprx = adapter.addWithUUID(CVoipAuthenticator())
|
||||
self.auth = MumbleServer.ServerUpdatingAuthenticatorPrx.uncheckedCast(authprx)
|
||||
|
||||
self.metacb = MumbleServer.MetaCallbackPrx.uncheckedCast(
|
||||
adapter.addWithUUID(metaCallback(self))
|
||||
)
|
||||
self.auth = MumbleServer.ServerUpdatingAuthenticatorPrx.uncheckedCast(
|
||||
adapter.addWithUUID(CVoipAuthenticator())
|
||||
)
|
||||
return self.attachCallbacks()
|
||||
|
||||
def attachCallbacks(self, quiet = False):
|
||||
"""
|
||||
Attaches all callbacks for meta and authenticators
|
||||
"""
|
||||
|
||||
# Ice.ConnectionRefusedException
|
||||
#debug('Attaching callbacks')
|
||||
try:
|
||||
if not quiet: info('Attaching meta callback')
|
||||
|
||||
def attachCallbacks(self, quiet=False):
|
||||
try:
|
||||
if not quiet:
|
||||
info('Attaching meta callback')
|
||||
self.meta.addCallback(self.metacb)
|
||||
|
||||
for server in self.meta.getBootedServers():
|
||||
if not cfg.murmur.servers or server.id() in cfg.murmur.servers:
|
||||
if not quiet: info('Setting authenticator for virtual server %d', server.id())
|
||||
if not quiet:
|
||||
info('Configuring authenticator for server %d', server.id())
|
||||
server.setAuthenticator(self.auth)
|
||||
|
||||
except (MumbleServer.InvalidSecretException, Ice.UnknownUserException, Ice.ConnectionRefusedException) as e:
|
||||
if isinstance(e, Ice.ConnectionRefusedException):
|
||||
error('Server refused connection')
|
||||
elif isinstance(e, MumbleServer.InvalidSecretException) or \
|
||||
isinstance(e, Ice.UnknownUserException) and (e.unknown == 'MumbleServer::InvalidSecretException'):
|
||||
error('Invalid ice secret')
|
||||
else:
|
||||
# We do not actually want to handle this one, re-raise it
|
||||
raise e
|
||||
|
||||
self.connected = True
|
||||
return True
|
||||
except (MumbleServer.InvalidSecretException, Ice.UnknownUserException) as e:
|
||||
error('Connection failed: %s', str(e))
|
||||
self.connected = False
|
||||
return False
|
||||
|
||||
self.connected = True
|
||||
return True
|
||||
|
||||
def checkConnection(self):
|
||||
"""
|
||||
Tries reapplies all callbacks to make sure the authenticator
|
||||
survives server restarts and disconnects.
|
||||
"""
|
||||
#debug('Watchdog run')
|
||||
|
||||
try:
|
||||
if not self.attachCallbacks(quiet = not self.failedWatch):
|
||||
self.failedWatch = True
|
||||
else:
|
||||
self.failedWatch = False
|
||||
self.attachCallbacks(quiet=not self.failedWatch)
|
||||
self.failedWatch = False
|
||||
except Ice.Exception as e:
|
||||
error('Failed connection check, will retry in next watchdog run (%ds)', cfg.ice.watchdog)
|
||||
debug(str(e))
|
||||
error('Connection check failed: %s', str(e))
|
||||
self.failedWatch = True
|
||||
|
||||
# Renew the timer
|
||||
self.watchdog = Timer(cfg.ice.watchdog, self.checkConnection)
|
||||
self.watchdog.start()
|
||||
|
||||
@ -431,12 +286,6 @@ def do_main_program():
|
||||
@fortifyIceFu(authenticateFortifyResult)
|
||||
@checkSecret
|
||||
def authenticate(self, name, pw, certlist, certhash, strong, current = None):
|
||||
"""
|
||||
This function is called to authenticate a user
|
||||
"""
|
||||
debug(f'The user\'s password: {pw}')
|
||||
|
||||
# Search for the user in the database
|
||||
FALL_THROUGH = -2
|
||||
AUTH_REFUSED = -1
|
||||
|
||||
@ -458,7 +307,7 @@ def do_main_program():
|
||||
try:
|
||||
user = User.objects.get(username=name)
|
||||
debug('User found: %s', user.username)
|
||||
if user.check_password(pw):
|
||||
if user.check_password(pw) and pw not in ('', None):
|
||||
# Successful authentication
|
||||
uid = user.id + cfg.user.id_offset
|
||||
groups = [group.name for group in user.groups.all()]
|
||||
@ -470,48 +319,8 @@ def do_main_program():
|
||||
except User.DoesNotExist:
|
||||
info('Refuse Connection for unknown user "%s"', name)
|
||||
return (AUTH_REFUSED, None, None)
|
||||
|
||||
if name == 'SuperUser':
|
||||
debug('Forced fall through for SuperUser')
|
||||
return (FALL_THROUGH, None, None)
|
||||
|
||||
try:
|
||||
sql = 'SELECT id_member, passwd, id_group, member_name, real_name, additional_groups, is_activated FROM %smembers WHERE LOWER(member_name) = LOWER(%%s)' % cfg.database.prefix
|
||||
cur = threadDB.execute(sql, [name])
|
||||
except threadDbException:
|
||||
return (FALL_THROUGH, None, None)
|
||||
|
||||
res = cur.fetchone()
|
||||
cur.close()
|
||||
if not res:
|
||||
info('Fall through for unknown user "%s"', name)
|
||||
return (FALL_THROUGH, None, None)
|
||||
|
||||
uid, upw, ugroupid, uname, urealname, uadditgroups, activated = res
|
||||
|
||||
if activated == 1 and smf_check_hash(pw, upw, uname):
|
||||
# Authenticated, fetch group memberships
|
||||
try:
|
||||
if uadditgroups:
|
||||
groupids = str(ugroupid) + ',' + uadditgroups
|
||||
else:
|
||||
groupids = str(ugroupid)
|
||||
|
||||
sql = 'SELECT group_name FROM %smembergroups WHERE id_group IN (%s)' % (cfg.database.prefix, groupids)
|
||||
cur = threadDB.execute(sql)
|
||||
except threadDbException:
|
||||
return (FALL_THROUGH, None, None)
|
||||
|
||||
groups = cur.fetchall()
|
||||
cur.close()
|
||||
if groups:
|
||||
groups = [a[0] for a in groups]
|
||||
|
||||
info('User authenticated: "%s" (%d)', name, uid + cfg.user.id_offset)
|
||||
debug('Group memberships: %s', str(groups))
|
||||
return (uid + cfg.user.id_offset, entity_decode(urealname), groups)
|
||||
|
||||
info('Failed authentication attempt for user: "%s" (%d)', name, uid + cfg.user.id_offset)
|
||||
info('Failed authentication attempt for user: "%s"', name)
|
||||
return (AUTH_REFUSED, None, None)
|
||||
|
||||
@fortifyIceFu((False, None))
|
||||
@ -536,21 +345,6 @@ def do_main_program():
|
||||
if name == 'SuperUser':
|
||||
debug('nameToId SuperUser -> forced fall through')
|
||||
return FALL_THROUGH
|
||||
|
||||
try:
|
||||
sql = 'SELECT id_member FROM %smembers WHERE LOWER(member_name) = LOWER(%%s)' % cfg.database.prefix
|
||||
cur = threadDB.execute(sql, [name])
|
||||
except threadDbException:
|
||||
return FALL_THROUGH
|
||||
|
||||
res = cur.fetchone()
|
||||
cur.close()
|
||||
if not res:
|
||||
debug('nameToId %s -> ?', name)
|
||||
return FALL_THROUGH
|
||||
|
||||
debug('nameToId %s -> %d', name, (res[0] + cfg.user.id_offset))
|
||||
return res[0] + cfg.user.id_offset
|
||||
|
||||
@fortifyIceFu("")
|
||||
@checkSecret
|
||||
@ -565,23 +359,6 @@ def do_main_program():
|
||||
return FALL_THROUGH
|
||||
bbid = id - cfg.user.id_offset
|
||||
|
||||
# Fetch the user from the database
|
||||
try:
|
||||
sql = 'SELECT member_name FROM %smembers WHERE id_member = %%s' % cfg.database.prefix
|
||||
cur = threadDB.execute(sql, [bbid])
|
||||
except threadDbException:
|
||||
return FALL_THROUGH
|
||||
|
||||
res = cur.fetchone()
|
||||
cur.close()
|
||||
if res:
|
||||
if res[0] == 'SuperUser':
|
||||
debug('idToName %d -> "SuperUser" catched')
|
||||
return FALL_THROUGH
|
||||
|
||||
debug('idToName %d -> "%s"', id, res[0])
|
||||
return res[0]
|
||||
|
||||
debug('idToName %d -> ?', id)
|
||||
return FALL_THROUGH
|
||||
|
||||
@ -599,66 +376,6 @@ def do_main_program():
|
||||
debug('idToTexture %d -> fall through', id)
|
||||
return FALL_THROUGH
|
||||
|
||||
# Otherwise get the users texture from smf
|
||||
bbid = id - cfg.user.id_offset
|
||||
try:
|
||||
sql = 'SELECT avatar FROM %smembers WHERE id_member = %%s' % cfg.database.prefix
|
||||
cur = threadDB.execute(sql, [bbid])
|
||||
except threadDbException:
|
||||
return FALL_THROUGH
|
||||
res = cur.fetchone()
|
||||
cur.close()
|
||||
if not res:
|
||||
debug('idToTexture %d -> user unknown, fall through', id)
|
||||
return FALL_THROUGH
|
||||
avatar = res[0]
|
||||
|
||||
if not avatar:
|
||||
# Either the user has none or it is in the attachments, check there
|
||||
try:
|
||||
sql = '''SELECT id_attach, file_hash, filename, attachment_type FROM %sattachments WHERE approved = true AND
|
||||
(attachment_type = 0 OR attachment_type = 1) AND id_member = %%s''' % cfg.database.prefix
|
||||
cur = threadDB.execute(sql, [bbid])
|
||||
except threadDbException:
|
||||
return FALL_THROUGH
|
||||
|
||||
res = cur.fetchone()
|
||||
cur.close()
|
||||
if not res:
|
||||
# No uploaded avatar found, seems like the user didn't set one
|
||||
debug('idToTexture %d -> no texture available for this user, fall through', id)
|
||||
return FALL_THROUGH
|
||||
|
||||
fid, fhash, filename, fattachtype = res
|
||||
if cfg.forum.path.startswith('file://'):
|
||||
# We are supposed to load this from the local fs
|
||||
avatar_file = cfg.forum.path + 'attachments/%d_%s' % (fid, fhash)
|
||||
elif fattachtype == 0:
|
||||
avatar_file = cfg.forum.path + 'index.php?action=dlattach;attach=%d;type=avatar' % fid
|
||||
elif fattachtype == 1:
|
||||
avatar_file = cfg.forum.path + 'avatars/' + filename
|
||||
elif "://" in avatar:
|
||||
# ...or it is a external link
|
||||
avatar_file = avatar
|
||||
else:
|
||||
warning("avatar with an unexpected value, fall through")
|
||||
return FALL_THROUGH
|
||||
|
||||
if avatar_file in self.texture_cache:
|
||||
return self.texture_cache[avatar_file]
|
||||
|
||||
try:
|
||||
handle = urllib.request.urlopen(avatar_file)
|
||||
filecontent = handle.read()
|
||||
handle.close()
|
||||
except urllib.error.URLError as e:
|
||||
warning('Image download for "%s" (%d) failed: %s', avatar_file, id, str(e))
|
||||
return FALL_THROUGH
|
||||
|
||||
self.texture_cache[avatar_file] = filecontent
|
||||
|
||||
return self.texture_cache[avatar_file]
|
||||
|
||||
@fortifyIceFu(-2)
|
||||
@checkSecret
|
||||
def registerUser(self, name, current = None):
|
||||
@ -682,31 +399,6 @@ def do_main_program():
|
||||
# but we can make murmur delete all additional information it got this way.
|
||||
debug('unregisterUser %d -> fall through', id)
|
||||
return FALL_THROUGH
|
||||
|
||||
@fortifyIceFu({})
|
||||
@checkSecret
|
||||
def getRegisteredUsers(self, filter, current = None):
|
||||
"""
|
||||
Returns a list of usernames in the smf database which contain
|
||||
filter as a substring.
|
||||
"""
|
||||
|
||||
if not filter:
|
||||
filter = '%'
|
||||
|
||||
try:
|
||||
sql = 'SELECT id_member, member_name FROM %smembers WHERE is_activated = 1 AND member_name LIKE %%s' % cfg.database.prefix
|
||||
cur = threadDB.execute(sql, [filter])
|
||||
except threadDbException:
|
||||
return {}
|
||||
|
||||
res = cur.fetchall()
|
||||
cur.close()
|
||||
if not res:
|
||||
debug('getRegisteredUsers -> empty list for filter "%s"', filter)
|
||||
return {}
|
||||
debug ('getRegisteredUsers -> %d results for filter "%s"', len(res), filter)
|
||||
return dict([(a + cfg.user.id_offset, b) for a,b in res])
|
||||
|
||||
@fortifyIceFu(-1)
|
||||
@checkSecret
|
||||
@ -767,9 +459,7 @@ def do_main_program():
|
||||
def error(self, message):
|
||||
self._log.error(message)
|
||||
|
||||
#
|
||||
#--- Start of authenticator
|
||||
#
|
||||
# === Start of authenticator ===
|
||||
info('Starting cvoip authenticator')
|
||||
initdata = Ice.InitializationData()
|
||||
initdata.properties = Ice.createProperties([], initdata.properties)
|
||||
@ -784,30 +474,7 @@ def do_main_program():
|
||||
state = app.main(sys.argv[:1], initData = initdata)
|
||||
info('Shutdown complete')
|
||||
|
||||
|
||||
|
||||
#
|
||||
#--- Python implementation of the smf check hash function
|
||||
#
|
||||
def smf_check_hash(password, hash, username):
|
||||
"""
|
||||
Python implementation of the smf check hash function
|
||||
"""
|
||||
ret = False
|
||||
|
||||
try:
|
||||
# SMF 2.1 uses a bcrypt hash, try that first
|
||||
ret = bcrypt.hashpw((username.lower() + password).encode('utf-8'), hash.encode('utf-8')) == hash
|
||||
except ValueError:
|
||||
# The sha1 password hash from SMF 2.0 and earlier will cause a salt value error
|
||||
# In that case, try the legacy sha1 hash
|
||||
ret = sha1((username.lower() + password).encode('utf-8')).hexdigest() == hash
|
||||
|
||||
return ret
|
||||
|
||||
#
|
||||
#--- Start of program
|
||||
#
|
||||
# === Entry Point ===
|
||||
if __name__ == '__main__':
|
||||
# Parse commandline options
|
||||
parser = OptionParser()
|
||||
@ -835,17 +502,9 @@ if __name__ == '__main__':
|
||||
except Exception as e:
|
||||
print('Fatal error, could not load config file from "%s"' % cfgfile, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
db = __import__(cfg.database.lib)
|
||||
except ImportError as e:
|
||||
print('Fatal error, could not import database library "%s", '\
|
||||
'please install the missing dependency and restart the authenticator' % cfg.database.lib, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Initialize logger
|
||||
if cfg.log.file:
|
||||
if cfg.log.file and option.logfile:
|
||||
try:
|
||||
logfile = open(cfg.log.file, 'a')
|
||||
except IOError as e:
|
||||
@ -885,8 +544,4 @@ if __name__ == '__main__':
|
||||
try:
|
||||
do_main_program()
|
||||
finally:
|
||||
context.__exit__(None, None, None)
|
||||
|
||||
|
||||
# Change django dB to postgres - Done
|
||||
# An extra option to display foreground logs - Done
|
||||
context.__exit__(None, None, None)
|
||||
@ -1,18 +1,15 @@
|
||||
annotated-types==0.7.0
|
||||
anyio==4.9.0
|
||||
asgiref==3.8.1
|
||||
bcrypt==4.3.0
|
||||
Django==5.2.2
|
||||
idna==3.10
|
||||
lockfile==0.12.2
|
||||
mysqlclient==2.2.7
|
||||
psycopg==3.2.9
|
||||
psycopg-binary==3.2.9
|
||||
pydantic==2.11.5
|
||||
pydantic_core==2.33.2
|
||||
python-daemon==3.1.2
|
||||
sniffio==1.3.1
|
||||
sqlparse==0.5.3
|
||||
starlette==0.46.2
|
||||
typing-inspection==0.4.1
|
||||
typing_extensions==4.14.0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user