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 configuration
|
||||||
[django]
|
[django]
|
||||||
;If true, the authenticator will use Django to handle user authentication instead of the database
|
;If true, the authenticator will use Django to handle user authentication instead of the database
|
||||||
|
|||||||
445
CVoipAuth.py
445
CVoipAuth.py
@ -1,41 +1,23 @@
|
|||||||
import sys
|
import sys
|
||||||
import Ice
|
import Ice
|
||||||
import _thread
|
|
||||||
import urllib.request, urllib.error, urllib.parse
|
|
||||||
import logging
|
import logging
|
||||||
import configparser
|
import configparser
|
||||||
import bcrypt
|
from threading import Timer
|
||||||
|
from optparse import OptionParser
|
||||||
from threading import Timer
|
from logging import debug, info, warning, error, critical, exception, getLogger
|
||||||
from optparse import OptionParser
|
|
||||||
from logging import (debug,
|
|
||||||
info,
|
|
||||||
warning,
|
|
||||||
error,
|
|
||||||
critical,
|
|
||||||
exception,
|
|
||||||
getLogger)
|
|
||||||
|
|
||||||
from hashlib import sha1
|
|
||||||
|
|
||||||
|
# === Configuration Helpers ===
|
||||||
def x2bool(s):
|
def x2bool(s):
|
||||||
"""Helper function to convert strings from the config to bool"""
|
"""Convert config strings to boolean"""
|
||||||
if isinstance(s, bool):
|
if isinstance(s, bool):
|
||||||
return s
|
return s
|
||||||
elif isinstance(s, str):
|
elif isinstance(s, str):
|
||||||
return s.lower() in ['1', 'true']
|
return s.lower() in ['1', 'true']
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
|
|
||||||
#
|
# === Default Configuration ===
|
||||||
#--- Default configuration values
|
|
||||||
#
|
|
||||||
cfgfile = 'CVoipAuth.ini'
|
cfgfile = 'CVoipAuth.ini'
|
||||||
default = {'database':(('lib', str, 'MySQLdb'),
|
default = {'django':(('enabled', x2bool, False),
|
||||||
('password', str, 'secret'),
|
|
||||||
('host', str, '127.0.0.1'),
|
|
||||||
('port', int, 3306)),
|
|
||||||
|
|
||||||
'django':(('enabled', x2bool, False),
|
|
||||||
('project', str, 'CVoipPanel'),
|
('project', str, 'CVoipPanel'),
|
||||||
('settings', str, 'CVoipPanel.settings')),
|
('settings', str, 'CVoipPanel.settings')),
|
||||||
|
|
||||||
@ -61,35 +43,29 @@ default = {'database':(('lib', str, 'MySQLdb'),
|
|||||||
'log':(('level', int, logging.DEBUG),
|
'log':(('level', int, logging.DEBUG),
|
||||||
('file', str, 'CVoipAuth.log'))}
|
('file', str, 'CVoipAuth.log'))}
|
||||||
|
|
||||||
#
|
# === Helper classes ===
|
||||||
#--- Helper classes
|
|
||||||
#
|
|
||||||
class config(object):
|
class config(object):
|
||||||
"""
|
|
||||||
Small abstraction for config loading
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, filename = None, default = None):
|
def __init__(self, filename = None, default = None):
|
||||||
if not filename or not default: return
|
if not filename or not default: return
|
||||||
cfg = configparser.ConfigParser()
|
cfg = configparser.ConfigParser()
|
||||||
cfg.optionxform = str
|
cfg.optionxform = str
|
||||||
cfg.read(filename)
|
cfg.read(filename)
|
||||||
|
for section, values in default.items():
|
||||||
for h,v in default.items():
|
if not values:
|
||||||
if not v:
|
|
||||||
# Output this whole section as a list of raw key/value tuples
|
|
||||||
try:
|
try:
|
||||||
self.__dict__[h] = cfg.items(h)
|
self.__dict__[section] = cfg.items(section)
|
||||||
except configparser.NoSectionError:
|
except configparser.NoSectionError:
|
||||||
self.__dict__[h] = []
|
self.__dict__[section] = []
|
||||||
else:
|
else:
|
||||||
self.__dict__[h] = config()
|
self.__dict__[section] = config()
|
||||||
for name, conv, vdefault in v:
|
for name, conv, vdefault in values:
|
||||||
try:
|
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):
|
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):
|
def entity_decode(string):
|
||||||
"""
|
"""
|
||||||
Python reverse implementation of php htmlspecialchars
|
Python reverse implementation of php htmlspecialchars
|
||||||
@ -118,87 +94,7 @@ def entity_encode(string):
|
|||||||
ret = ret.replace(s, t)
|
ret = ret.replace(s, t)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
class threadDbException(Exception): pass
|
# === Main Application ===
|
||||||
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)
|
|
||||||
|
|
||||||
def do_main_program():
|
def do_main_program():
|
||||||
#
|
#
|
||||||
#--- Authenticator implementation
|
#--- Authenticator implementation
|
||||||
@ -222,101 +118,60 @@ def do_main_program():
|
|||||||
if cfg.ice.watchdog > 0:
|
if cfg.ice.watchdog > 0:
|
||||||
self.failedWatch = True
|
self.failedWatch = True
|
||||||
self.checkConnection()
|
self.checkConnection()
|
||||||
|
|
||||||
# Serve till we are stopped
|
|
||||||
self.communicator().waitForShutdown()
|
self.communicator().waitForShutdown()
|
||||||
self.watchdog.cancel()
|
|
||||||
|
|
||||||
|
if hasattr(self, 'watchdog'):
|
||||||
|
self.watchdog.cancel()
|
||||||
if self.interrupted():
|
if self.interrupted():
|
||||||
warning('Caught interrupt, shutting down')
|
warning('Interrupt received - shutting down')
|
||||||
|
|
||||||
threadDB.disconnect()
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def initializeIceConnection(self):
|
def initializeIceConnection(self):
|
||||||
"""
|
|
||||||
Establishes the two-way Ice connection and adds the authenticator to the
|
|
||||||
configured servers
|
|
||||||
"""
|
|
||||||
ice = self.communicator()
|
ice = self.communicator()
|
||||||
|
|
||||||
if cfg.ice.secret:
|
if cfg.ice.secret:
|
||||||
debug('Using shared ice secret')
|
|
||||||
ice.getImplicitContext().put("secret", cfg.ice.secret)
|
ice.getImplicitContext().put("secret", cfg.ice.secret)
|
||||||
elif not cfg.glacier.enabled:
|
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 Ice at %s:%d', cfg.ice.host, cfg.ice.port)
|
||||||
#info('Connecting to Glacier2 server (%s:%d)', glacier_host, glacier_port)
|
proxy = ice.stringToProxy(f'Meta:tcp -h {cfg.ice.host} -p {cfg.ice.port}')
|
||||||
error('Glacier support not implemented yet')
|
self.meta = MumbleServer.MetaPrx.uncheckedCast(proxy)
|
||||||
#TODO: Implement this
|
|
||||||
|
|
||||||
info('Connecting to Ice server (%s:%d)', cfg.ice.host, cfg.ice.port)
|
adapter = ice.createObjectAdapterWithEndpoints('Callback.Client', f'tcp -h {cfg.ice.host}')
|
||||||
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)
|
|
||||||
adapter.activate()
|
adapter.activate()
|
||||||
|
|
||||||
metacbprx = adapter.addWithUUID(metaCallback(self))
|
self.metacb = MumbleServer.MetaCallbackPrx.uncheckedCast(
|
||||||
self.metacb = MumbleServer.MetaCallbackPrx.uncheckedCast(metacbprx)
|
adapter.addWithUUID(metaCallback(self))
|
||||||
|
)
|
||||||
authprx = adapter.addWithUUID(CVoipAuthenticator())
|
self.auth = MumbleServer.ServerUpdatingAuthenticatorPrx.uncheckedCast(
|
||||||
self.auth = MumbleServer.ServerUpdatingAuthenticatorPrx.uncheckedCast(authprx)
|
adapter.addWithUUID(CVoipAuthenticator())
|
||||||
|
)
|
||||||
return self.attachCallbacks()
|
return self.attachCallbacks()
|
||||||
|
|
||||||
def attachCallbacks(self, quiet = False):
|
def attachCallbacks(self, quiet=False):
|
||||||
"""
|
|
||||||
Attaches all callbacks for meta and authenticators
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Ice.ConnectionRefusedException
|
|
||||||
#debug('Attaching callbacks')
|
|
||||||
try:
|
try:
|
||||||
if not quiet: info('Attaching meta callback')
|
if not quiet:
|
||||||
|
info('Attaching meta callback')
|
||||||
self.meta.addCallback(self.metacb)
|
self.meta.addCallback(self.metacb)
|
||||||
|
|
||||||
for server in self.meta.getBootedServers():
|
for server in self.meta.getBootedServers():
|
||||||
if not cfg.murmur.servers or server.id() in cfg.murmur.servers:
|
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)
|
server.setAuthenticator(self.auth)
|
||||||
|
self.connected = True
|
||||||
except (MumbleServer.InvalidSecretException, Ice.UnknownUserException, Ice.ConnectionRefusedException) as e:
|
return True
|
||||||
if isinstance(e, Ice.ConnectionRefusedException):
|
except (MumbleServer.InvalidSecretException, Ice.UnknownUserException) as e:
|
||||||
error('Server refused connection')
|
error('Connection failed: %s', str(e))
|
||||||
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 = False
|
self.connected = False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.connected = True
|
|
||||||
return True
|
|
||||||
|
|
||||||
def checkConnection(self):
|
def checkConnection(self):
|
||||||
"""
|
|
||||||
Tries reapplies all callbacks to make sure the authenticator
|
|
||||||
survives server restarts and disconnects.
|
|
||||||
"""
|
|
||||||
#debug('Watchdog run')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not self.attachCallbacks(quiet = not self.failedWatch):
|
self.attachCallbacks(quiet=not self.failedWatch)
|
||||||
self.failedWatch = True
|
self.failedWatch = False
|
||||||
else:
|
|
||||||
self.failedWatch = False
|
|
||||||
except Ice.Exception as e:
|
except Ice.Exception as e:
|
||||||
error('Failed connection check, will retry in next watchdog run (%ds)', cfg.ice.watchdog)
|
error('Connection check failed: %s', str(e))
|
||||||
debug(str(e))
|
|
||||||
self.failedWatch = True
|
self.failedWatch = True
|
||||||
|
|
||||||
# Renew the timer
|
|
||||||
self.watchdog = Timer(cfg.ice.watchdog, self.checkConnection)
|
self.watchdog = Timer(cfg.ice.watchdog, self.checkConnection)
|
||||||
self.watchdog.start()
|
self.watchdog.start()
|
||||||
|
|
||||||
@ -431,12 +286,6 @@ def do_main_program():
|
|||||||
@fortifyIceFu(authenticateFortifyResult)
|
@fortifyIceFu(authenticateFortifyResult)
|
||||||
@checkSecret
|
@checkSecret
|
||||||
def authenticate(self, name, pw, certlist, certhash, strong, current = None):
|
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
|
FALL_THROUGH = -2
|
||||||
AUTH_REFUSED = -1
|
AUTH_REFUSED = -1
|
||||||
|
|
||||||
@ -458,7 +307,7 @@ def do_main_program():
|
|||||||
try:
|
try:
|
||||||
user = User.objects.get(username=name)
|
user = User.objects.get(username=name)
|
||||||
debug('User found: %s', user.username)
|
debug('User found: %s', user.username)
|
||||||
if user.check_password(pw):
|
if user.check_password(pw) and pw not in ('', None):
|
||||||
# Successful authentication
|
# Successful authentication
|
||||||
uid = user.id + cfg.user.id_offset
|
uid = user.id + cfg.user.id_offset
|
||||||
groups = [group.name for group in user.groups.all()]
|
groups = [group.name for group in user.groups.all()]
|
||||||
@ -471,47 +320,7 @@ def do_main_program():
|
|||||||
info('Refuse Connection for unknown user "%s"', name)
|
info('Refuse Connection for unknown user "%s"', name)
|
||||||
return (AUTH_REFUSED, None, None)
|
return (AUTH_REFUSED, None, None)
|
||||||
|
|
||||||
if name == 'SuperUser':
|
info('Failed authentication attempt for user: "%s"', name)
|
||||||
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)
|
|
||||||
return (AUTH_REFUSED, None, None)
|
return (AUTH_REFUSED, None, None)
|
||||||
|
|
||||||
@fortifyIceFu((False, None))
|
@fortifyIceFu((False, None))
|
||||||
@ -537,21 +346,6 @@ def do_main_program():
|
|||||||
debug('nameToId SuperUser -> forced fall through')
|
debug('nameToId SuperUser -> forced fall through')
|
||||||
return 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("")
|
@fortifyIceFu("")
|
||||||
@checkSecret
|
@checkSecret
|
||||||
def idToName(self, id, current = None):
|
def idToName(self, id, current = None):
|
||||||
@ -565,23 +359,6 @@ def do_main_program():
|
|||||||
return FALL_THROUGH
|
return FALL_THROUGH
|
||||||
bbid = id - cfg.user.id_offset
|
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)
|
debug('idToName %d -> ?', id)
|
||||||
return FALL_THROUGH
|
return FALL_THROUGH
|
||||||
|
|
||||||
@ -599,66 +376,6 @@ def do_main_program():
|
|||||||
debug('idToTexture %d -> fall through', id)
|
debug('idToTexture %d -> fall through', id)
|
||||||
return FALL_THROUGH
|
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)
|
@fortifyIceFu(-2)
|
||||||
@checkSecret
|
@checkSecret
|
||||||
def registerUser(self, name, current = None):
|
def registerUser(self, name, current = None):
|
||||||
@ -683,31 +400,6 @@ def do_main_program():
|
|||||||
debug('unregisterUser %d -> fall through', id)
|
debug('unregisterUser %d -> fall through', id)
|
||||||
return FALL_THROUGH
|
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)
|
@fortifyIceFu(-1)
|
||||||
@checkSecret
|
@checkSecret
|
||||||
def setInfo(self, id, info, current = None):
|
def setInfo(self, id, info, current = None):
|
||||||
@ -767,9 +459,7 @@ def do_main_program():
|
|||||||
def error(self, message):
|
def error(self, message):
|
||||||
self._log.error(message)
|
self._log.error(message)
|
||||||
|
|
||||||
#
|
# === Start of authenticator ===
|
||||||
#--- Start of authenticator
|
|
||||||
#
|
|
||||||
info('Starting cvoip authenticator')
|
info('Starting cvoip authenticator')
|
||||||
initdata = Ice.InitializationData()
|
initdata = Ice.InitializationData()
|
||||||
initdata.properties = Ice.createProperties([], initdata.properties)
|
initdata.properties = Ice.createProperties([], initdata.properties)
|
||||||
@ -784,30 +474,7 @@ def do_main_program():
|
|||||||
state = app.main(sys.argv[:1], initData = initdata)
|
state = app.main(sys.argv[:1], initData = initdata)
|
||||||
info('Shutdown complete')
|
info('Shutdown complete')
|
||||||
|
|
||||||
|
# === Entry Point ===
|
||||||
|
|
||||||
#
|
|
||||||
#--- 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
|
|
||||||
#
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Parse commandline options
|
# Parse commandline options
|
||||||
parser = OptionParser()
|
parser = OptionParser()
|
||||||
@ -836,16 +503,8 @@ if __name__ == '__main__':
|
|||||||
print('Fatal error, could not load config file from "%s"' % cfgfile, file=sys.stderr)
|
print('Fatal error, could not load config file from "%s"' % cfgfile, file=sys.stderr)
|
||||||
sys.exit(1)
|
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
|
# Initialize logger
|
||||||
if cfg.log.file:
|
if cfg.log.file and option.logfile:
|
||||||
try:
|
try:
|
||||||
logfile = open(cfg.log.file, 'a')
|
logfile = open(cfg.log.file, 'a')
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
@ -886,7 +545,3 @@ if __name__ == '__main__':
|
|||||||
do_main_program()
|
do_main_program()
|
||||||
finally:
|
finally:
|
||||||
context.__exit__(None, None, None)
|
context.__exit__(None, None, None)
|
||||||
|
|
||||||
|
|
||||||
# Change django dB to postgres - Done
|
|
||||||
# An extra option to display foreground logs - Done
|
|
||||||
@ -1,18 +1,15 @@
|
|||||||
annotated-types==0.7.0
|
annotated-types==0.7.0
|
||||||
anyio==4.9.0
|
anyio==4.9.0
|
||||||
asgiref==3.8.1
|
asgiref==3.8.1
|
||||||
bcrypt==4.3.0
|
|
||||||
Django==5.2.2
|
Django==5.2.2
|
||||||
idna==3.10
|
idna==3.10
|
||||||
lockfile==0.12.2
|
lockfile==0.12.2
|
||||||
mysqlclient==2.2.7
|
|
||||||
psycopg==3.2.9
|
psycopg==3.2.9
|
||||||
psycopg-binary==3.2.9
|
psycopg-binary==3.2.9
|
||||||
pydantic==2.11.5
|
pydantic==2.11.5
|
||||||
pydantic_core==2.33.2
|
pydantic_core==2.33.2
|
||||||
python-daemon==3.1.2
|
python-daemon==3.1.2
|
||||||
sniffio==1.3.1
|
sniffio==1.3.1
|
||||||
sqlparse==0.5.3
|
|
||||||
starlette==0.46.2
|
starlette==0.46.2
|
||||||
typing-inspection==0.4.1
|
typing-inspection==0.4.1
|
||||||
typing_extensions==4.14.0
|
typing_extensions==4.14.0
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user