Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mumble-voip/mumble.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Hacker <dd0t@users.sourceforge.net>2010-02-02 02:06:10 +0300
committerStefan Hacker <dd0t@users.sourceforge.net>2010-02-02 02:06:57 +0300
commit72184139c87830835d65842fbe6f009dd7e6c7eb (patch)
tree12a52c333bc7f8ff7c2a77e94c1fa624efd82e6e /scripts
parent99a85467a66fc2ed1b54daa45240403362b603c1 (diff)
phpBB3 and SMF authenticator moved to http://gitorious.org/mumble-scripts/ . From now one all non-sample scripts will be placed in this repository.
Diffstat (limited to 'scripts')
-rw-r--r--scripts/phpBB3auth.ini47
-rw-r--r--scripts/phpBB3auth.py725
-rw-r--r--scripts/smfauth.ini53
-rw-r--r--scripts/smfauth.py707
4 files changed, 0 insertions, 1532 deletions
diff --git a/scripts/phpBB3auth.ini b/scripts/phpBB3auth.ini
deleted file mode 100644
index d33a295b2..000000000
--- a/scripts/phpBB3auth.ini
+++ /dev/null
@@ -1,47 +0,0 @@
-;Database configuration
-[database]
-;Only tested with MySQL at the moment
-lib = MySQLdb
-name = phpbb3
-user = phpbb3
-password = secret
-prefix = phpbb_
-host = 127.0.0.1
-port = 3306
-
-;Player configuration
-[user]
-;If you do not already know what it is just leave it as it is
-id_offset = 1000000000
-;If enabled avatars are automatically set as user textures
-avatar_enable = False
-avatar_path = http://localhost/phpBB3/download/file.php?avatar=
-
-;If avatar fetching is enabled and the following options are configured
-;the username will be overlayed over the avatar from the board.
-;The font can be any truetype font (default is verdana), the x,y variables
-;define the offset and fill the color used to draw the text
-avatar_username_enable = True
-avatar_username_font = verdana.ttf
-avatar_username_fontsize = 30
-avatar_username_x = 65
-avatar_username_y = 10
-avatar_username_fill = #FFCC01
-
-
-;Ice configuration
-[ice]
-host = 127.0.0.1
-port = 6502
-slice = Murmur.ice
-
-;Murmur configuration
-[murmur]
-;List of virtual server IDs, empty = all
-servers =
-
-;Logging configuration
-[log]
-; Available loglevels: 10 = DEBUG (default) | 20 = INFO | 30 = WARNING | 40 = ERROR
-level =
-file = phpBB3auth.log \ No newline at end of file
diff --git a/scripts/phpBB3auth.py b/scripts/phpBB3auth.py
deleted file mode 100644
index 2fb7bc45e..000000000
--- a/scripts/phpBB3auth.py
+++ /dev/null
@@ -1,725 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8
-
-#
-# phpBB3auth.py - Sample script to demonstrate authentication against
-# an existing phpBB3 forum database.
-#
-# Requirements:
-# * python >=2.4 and the following python modules:
-# * ice-python
-# * PIL >=1.1.5 (only if avatar import is enabled)
-# * MySQLdb
-# * daemon (when run as a daemon)
-
-import sys
-import Ice
-import thread
-import logging
-import ConfigParser
-
-from logging import (debug,
- info,
- warning,
- error,
- critical,
- getLogger)
-from optparse import OptionParser
-try:
- from hashlib import md5
-except ImportError: # python 2.4 compat
- from md5 import md5
-
-def x2bool(s):
- """Helper function to convert strings from the config to bool"""
- if isinstance(s, bool):
- return s
- elif isinstance(s, basestring):
- return s.lower() in ['1', 'true']
- raise ValueError()
-
-#
-#--- Default configuration values
-#
-cfgfile = 'phpBB3auth.ini'
-user_texture_resolution = (600,60)
-default = {'database':(('lib', str, 'MySQLdb'),
- ('name', str, 'phpbb3'),
- ('user', str, 'phpbb3'),
- ('password', str, 'secret'),
- ('prefix', str, 'phpbb_'),
- ('host', str, '127.0.0.1'),
- ('port', int, 3306)),
-
- 'user':(('id_offset', int, 1000000000),
- ('avatar_enable', x2bool, False),
- ('avatar_path', str, 'http://localhost/phpBB3/download.php?avatar='),
- ('avatar_username_enable', x2bool, True),
- ('avatar_username_font', str, 'verdana.ttf'),
- ('avatar_username_fontsize', int, 30),
- ('avatar_username_x', int, 65),
- ('avatar_username_y', int, 10),
- ('avatar_username_fill', str, '#FF0000')),
-
- 'ice':(('host', str, '127.0.0.1'),
- ('port', int, 6502),
- ('slice', str, 'Murmur.ice')),
-
- 'iceraw':None,
-
- 'murmur':(('servers', lambda x:map(int, x.split(',')), []),),
- 'glacier':(('enabled', x2bool, False),
- ('user', str, 'phpBB3auth'),
- ('password', str, 'secret'),
- ('host', str, 'localhost'),
- ('port', int, '4063')),
-
- 'log':(('level', int, logging.DEBUG),
- ('file', str, 'phpBB3auth.log'))}
-
-#
-#--- 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.iteritems():
- if not v:
- # Output this whole section as a list of raw key/value tuples
- try:
- self.__dict__[h] = cfg.items(h)
- except ConfigParser.NoSectionError:
- self.__dict__[h] = []
- else:
- self.__dict__[h] = config()
- for name, conv, vdefault in v:
- try:
- self.__dict__[h].__dict__[name] = conv(cfg.get(h, name))
- except (ValueError, ConfigParser.NoSectionError, ConfigParser.NoOptionError):
- self.__dict__[h].__dict__[name] = vdefault
-
-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')
- except db.Error, 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):
- c = cls.cursor()
- try:
- c.execute(*args, **kwargs)
- except db.OperationalError, e:
- error('Database operational error %d: %s', e.args[0], e.args[1])
- c.close()
- cls.invalidate_connection()
- 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():
- #
- #--- Authenticator implementation
- # All of this has to go in here so we can correctly daemonize the tool
- # without loosing the file descriptors opened by the Ice module
- Ice.loadSlice(cfg.ice.slice)
- import Murmur
-
- class phpBBauthenticatorApp(Ice.Application):
- def run(self, args):
- self.shutdownOnInterrupt()
-
- if not self.initializeIceConnection():
- return 1
-
- # Serve till we are stopped
- self.communicator().waitForShutdown()
-
- if self.interrupted():
- warning('Caught interrupt, shutting down')
-
- threadDB.disconnect()
- return 0
-
- def initializeIceConnection(self):
- """
- Establishes the two-way Ice connection and adds the authenticator to the
- configured servers
- """
- ice = self.communicator()
-
- 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))
- try:
- meta = Murmur.MetaPrx.checkedCast(base)
- except Ice.LocalException, e:
- error('Could not connect to Ice server, error %d: %s', e.error, str(e).replace('\n', ' '))
- return False
-
- adapter = ice.createObjectAdapterWithEndpoints('Callback.Client', 'tcp -h %s' % cfg.ice.host)
- adapter.activate()
-
- for server in meta.getBootedServers():
- if not cfg.murmur.servers or server.id() in cfg.murmur.servers:
- info('Setting authenticator for server %d', server.id())
- authprx = adapter.addWithUUID(phpBBauthenticator(server, adapter))
- auth = Murmur.ServerUpdatingAuthenticatorPrx.uncheckedCast(authprx)
- server.setAuthenticator(auth)
- return True
-
- class phpBBauthenticator(Murmur.ServerUpdatingAuthenticator):
- texture_cache = {}
- def __init__(self, server, adapter):
- Murmur.ServerUpdatingAuthenticator.__init__(self)
- self.server = server
-
- if cfg.user.avatar_enable and cfg.user.avatar_username_enable:
- # Load font
- try:
- self.font = ImageFont.truetype(cfg.user.avatar_username_font, cfg.user.avatar_username_fontsize)
- except IOError, e:
- error("Could not load font for username texture overlay from '%s': %s", cfg.user.avatar_username_font, e)
- self.font = None
- else:
- self.font = None
-
-
- def authenticate(self, name, pw, certlist, certhash, strong, current = None):
- """
- This function is called to authenticate a user
- """
-
- # Search for the user in the database
- FALL_THROUGH = -2
- AUTH_REFUSED = -1
-
- if name == 'SuperUser':
- debug('Forced fall through for SuperUser')
- return (FALL_THROUGH, None, None)
-
- try:
- sql = 'SELECT user_id, user_password, user_type, username FROM %susers WHERE (user_type = 0 OR user_type = 3) AND LOWER(username) = 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, utp, unm = res
- if phpbb_check_hash(pw, upw):
- # Authenticated, fetch group memberships
- try:
- sql = 'SELECT group_name FROM %suser_group JOIN %sgroups USING (group_id) WHERE user_id = %%s' % (cfg.database.prefix, cfg.database.prefix)
- cur = threadDB.execute(sql, uid)
- except threadDbException:
- return (FALL_THROUGH, None, None)
-
- res = cur.fetchall()
- cur.close()
- if res:
- res = [a[0] for a in res]
-
- info('User authenticated: "%s" (%d)', name, uid + cfg.user.id_offset)
- debug('Group memberships: %s', str(res))
- return (uid + cfg.user.id_offset, name, res)
-
- info('Failed authentication attempt for user: "%s" (%d)', name, uid + cfg.user.id_offset)
- return (AUTH_REFUSED, None, None)
-
-
- def getInfo(self, id, current = None):
- """
- Gets called to fetch user specific information
- """
-
- # We do not expose any additional information so always fall through
- debug('getInfo for %d -> denied', id)
- return (False, None)
-
-
- def nameToId(self, name, current = None):
- """
- Gets called to get the id for a given username
- """
-
- FALL_THROUGH = -2
- if name == 'SuperUser':
- debug('nameToId SuperUser -> forced fall through')
- return FALL_THROUGH
-
- try:
- sql = 'SELECT user_id FROM %susers WHERE (user_type = 0 OR user_type = 3) AND LOWER(username) = 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
-
-
- def idToName(self, id, current = None):
- """
- Gets called to get the username for a given id
- """
-
- FALL_THROUGH = ""
- # Make sure the ID is in our range and transform it to the actual phpBB3 user id
- if id < cfg.user.id_offset:
- return FALL_THROUGH
- bbid = id - cfg.user.id_offset
-
- # Fetch the user from the database
- try:
- sql = 'SELECT username FROM %susers WHERE (user_type = 0 OR user_type = 3) AND user_id = %%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
-
-
- def idToTexture(self, id, current = None):
- """
- Gets called to get the corresponding texture for a user
- """
-
- FALL_THROUGH = ""
-
- debug('idToTexture for %d', id)
- if id < cfg.user.id_offset or not cfg.user.avatar_enable:
- debug('idToTexture %d -> fall through', id)
- return FALL_THROUGH
-
- # Otherwise get the users texture from phpBB3
- bbid = id - cfg.user.id_offset
- try:
- sql = 'SELECT username, user_avatar, user_avatar_type FROM %susers WHERE (user_type = 0 OR user_type = 3) AND user_id = %%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
- username, avatar_file, avatar_type = res
- if avatar_type != 1 and avatar_type != 2:
- debug('idToTexture %d -> no texture available for this user (%d), fall through', id, avatar_type)
- return FALL_THROUGH
-
- if avatar_file in self.texture_cache:
- return self.texture_cache[avatar_file]
-
- if avatar_type == 1:
- url = cfg.user.avatar_path + avatar_file
- else:
- url = avatar_file
-
- try:
- handle = urllib2.urlopen(url)
- file = StringIO.StringIO(handle.read())
- handle.close()
- except urllib2.URLError, e:
- warning('Image download for "%s" (%d) failed: %s', url, id, str(e))
- return FALL_THROUGH
-
- try:
- # Load image and scale it
- img = Image.open(file).convert("RGBA")
- img.thumbnail((user_texture_resolution[0],user_texture_resolution[1]), Image.ANTIALIAS)
- img = img.transform(user_texture_resolution,
- Image.EXTENT,
- (0, 0, user_texture_resolution[0], user_texture_resolution[1]))
-
- if cfg.user.avatar_username_enable and self.font:
- # Insert user name into picture
- draw = ImageDraw.Draw(img)
- draw.text((cfg.user.avatar_username_x, cfg.user.avatar_username_y),
- username,
- fill = cfg.user.avatar_username_fill,
- font = self.font)
-
- r,g,b,a = img.split()
- raw = Image.merge('RGBA', (b, g, r, a)).tostring()
- comp = compress(raw)
- res = pack('>L', len(raw)) + comp
- except Exception, e:
- warning('Image manipulation for "%s" (%d) failed', url, id)
- debug(e)
- return FALL_THROUGH
-
- self.texture_cache[avatar_file] = res
- return res
-
-
- def registerUser(self, name, current = None):
- """
- Gets called when the server is asked to register a user.
- """
-
- FALL_THROUGH = -2
- debug('registerUser "%s" -> fall through', name)
- return FALL_THROUGH
-
-
- def unregisterUser(self, id, current = None):
- """
- Gets called when the server is asked to unregister a user.
- """
-
- FALL_THROUGH = -1
- # Return -1 to fall through to internal server database, we will not modify the phpbb3 database
- # but we can make murmur delete all additional information it got this way.
- debug('unregisterUser %d -> fall through', id)
- return FALL_THROUGH
-
-
- def getRegisteredUsers(self, filter, current = None):
- """
- Returns a list of usernames in the phpBB3 database which contain
- filter as a substring.
- """
-
- if not filter:
- filter = '%'
-
- try:
- sql = 'SELECT user_id, username FROM %susers WHERE (user_type = 0 OR user_type = 3) AND username 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])
-
-
- def setInfo(self, id, info, current = None):
- """
- Gets called when the server is supposed to save additional information
- about a user to his database
- """
-
- FALL_THROUGH = -1
- # Return -1 to fall through to the internal server handler. We must not modify
- # the phpBB3 database so the additional information is stored in murmurs database
- debug('setInfo %d -> fall through', id)
- return FALL_THROUGH
-
-
- def setTexture(self, id, texture, current = None):
- """
- Gets called when the server is asked to update the user texture of a user
- """
-
- FAILED = 0
- FALL_THROUGH = -1
-
- if id < cfg.user.id_offset:
- debug('setTexture %d -> fall through', id)
- return FALL_THROUGH
-
- if cfg.user.avatar_enable:
- # Report a fail (0) as we will not update the avatar in the phpBB3 database.
- debug('setTexture %d -> failed', id)
- return FAILED
-
- # If we don't use textures from phpbb we let mumble save it
- debug('setTexture %d -> fall through', id)
- return FALL_THROUGH
-
-
- class CustomLogger(Ice.Logger):
- """
- Logger implementation to pipe Ice log messages into
- out own log
- """
-
- def __init__(self):
- Ice.Logger.__init__(self)
- self._log = getLogger("Ice")
-
- def _print(self, message):
- self._log.info(message)
-
- def trace(self, category, message):
- self._log.debug("Trace %s: %s", category, message)
-
- def warning(self, message):
- self._log.warning(message)
-
- def error(self, message):
- self._log.error(message)
-
- #
- #--- Start of authenticator
- #
- info('Starting phpBB3 mumble authenticator')
- initdata = Ice.InitializationData()
- initdata.properties = Ice.createProperties([], initdata.properties)
- for prop, val in cfg.iceraw:
- initdata.properties.setProperty(prop, val)
- initdata.logger = CustomLogger()
-
- app = phpBBauthenticatorApp()
- state = app.main(sys.argv[:1], initData = initdata)
- info('Shutdown complete')
-
-
-
-#
-#--- Python implementation of the phpBB3 check hash function (salted md5)
-#
-def _hash_encode64(sinput, count, itoa64):
- output = ''
- i = 0
- while True:
- value = ord(sinput[i])
- i += 1
- output += itoa64[value & 0x3f]
-
- if i < count:
- value |= (ord(sinput[i]) << 8)
-
- output += itoa64[(value >> 6) & 0x3f]
-
- if i >= count:
- break
- i += 1
-
- if i < count:
- value |= (ord(sinput[i]) << 16)
-
- output += itoa64[(value >> 12) & 0x3f]
-
- if i >= count:
- break
-
- i = i + 1
- output += itoa64[(value >> 18) & 0x3f]
- if i >= count:
- break
- return output
-
-def _hash_crypt_private(password, settings, itoa64):
- output = '*'
-
- if settings[0:3] != '$H$':
- return output
-
- try:
- count_log2 = itoa64.index(settings[3])
- except ValueError:
- return output
-
- if (count_log2 < 7) or (count_log2 > 30):
- return output
-
- count = 1 << count_log2
- salt = settings[4:12]
-
- if len(salt) != 8:
- return output
-
- hash = md5(salt + password).digest()
- while True:
- hash = md5(hash + password).digest()
- count = count - 1
- if count <= 0:
- break
-
- output = settings[0:12]
- output += _hash_encode64(hash, 16, itoa64)
-
- return output
-
-def phpbb_check_hash(password, hash):
- """
- Python implementation of the phpBB3 check hash function
- """
-
- itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
- if len(hash) == 34:
- return _hash_crypt_private(password, hash, itoa64) == hash
-
- return md5(password).hexdigest() == hash
-
-#
-#--- Start of program
-#
-if __name__ == '__main__':
- # Parse commandline options
- parser = OptionParser()
- parser.add_option('-i', '--ini',
- help = 'load configuration from INI', default = cfgfile)
- parser.add_option('-v', '--verbose', action='store_true', dest = 'verbose',
- help = 'verbose output [default]', default = True)
- parser.add_option('-q', '--quiet', action='store_false', dest = 'verbose',
- help = 'only error output')
- parser.add_option('-d', '--daemon', action='store_true', dest = 'force_daemon',
- help = 'run as daemon', default = False)
- parser.add_option('-a', '--app', action='store_true', dest = 'force_app',
- help = 'do not run as daemon', default = False)
- (option, args) = parser.parse_args()
-
- if option.force_daemon and option.force_app:
- parser.print_help()
- sys.exit(1)
-
- # Load configuration
- try:
- cfg = config(option.ini, default)
- except Exception, e:
- print>>sys.stderr, 'Fatal error, could not load config file from "%s"' % cfgfile
- sys.exit(1)
-
- # Do conditional imports
- if cfg.user.avatar_enable:
- # If we use avatars we need PIL to manipulate it and some other stuff for working with them
- try:
- import Image
- if cfg.user.avatar_username_enable:
- import ImageFont
- import ImageDraw
- except ImportError, e:
- print>>sys.stderr, 'Error, could not import PIL library, '\
- 'please install the missing dependency and restart the authenticator'
- sys.exit(1)
-
- import urllib2
- import StringIO
-
- from zlib import compress
- from struct import pack
-
- try:
- db = __import__(cfg.database.lib)
- except ImportError, e:
- print>>sys.stderr, 'Fatal error, could not import database library "%s", '\
- 'please install the missing dependency and restart the authenticator' % cfg.database.lib
- sys.exit(1)
-
-
- # Initialize logger
- if cfg.log.file:
- try:
- logfile = open(cfg.log.file, 'a')
- except IOError, e:
- print>>sys.stderr, 'Fatal error, could not open logfile "%s"' % cfg.log.file
- sys.exit(1)
- else:
- logfile = logging.sys.stderr
-
-
- if option.verbose:
- level = cfg.log.level
- else:
- level = logging.ERROR
-
- logging.basicConfig(level = level,
- format='%(asctime)s %(levelname)s %(message)s',
- stream = logfile)
-
- # As the default try to run as daemon. Silently degrade to running as a normal application if this fails
- # unless the user explicitly defined what he expected with the -a / -d parameter.
- try:
- if option.force_app:
- raise ImportError # Pretend that we couldn't import the daemon lib
- import daemon
- except ImportError:
- if option.force_daemon:
- print>>sys.stderr, 'Fatal error, could not daemonize process due to missing "daemon" library, ' \
- 'please install the missing dependency and restart the authenticator'
- sys.exit(1)
- do_main_program()
- else:
- context = daemon.DaemonContext(working_directory = sys.path[0],
- stderr = logfile)
- context.__enter__()
- try:
- do_main_program()
- finally:
- context.__exit__(None, None, None)
diff --git a/scripts/smfauth.ini b/scripts/smfauth.ini
deleted file mode 100644
index 2c0981cda..000000000
--- a/scripts/smfauth.ini
+++ /dev/null
@@ -1,53 +0,0 @@
-;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
-
-;Forum information
-[forum]
-; The path can either be the url to the forum or a local uri
-; like file:///var/www/htdocs/smf/ if the forum is located on
-; the same machine as the authenticator (make sure the permissions
-; are set correctly when using local paths
-path = http://localhost/smf/
-;Player configuration
-[user]
-;If you do not already know what it is just leave it as it is
-id_offset = 1000000000
-;If enabled avatars are automatically set as user textures
-avatar_enable = False
-
-;If avatar fetching is enabled and the following options are configured
-;the username will be overlayed over the avatar from the board.
-;The font can be any truetype font (default is verdana), the x,y variables
-;define the offset and fill the color used to draw the text
-avatar_username_enable = True
-avatar_username_font = verdana.ttf
-avatar_username_fontsize = 30
-avatar_username_x = 65
-avatar_username_y = 10
-avatar_username_fill = #FFCC01
-
-
-;Ice configuration
-[ice]
-host = 127.0.0.1
-port = 6502
-slice = Murmur.ice
-
-;Murmur configuration
-[murmur]
-;List of virtual server IDs, empty = all
-servers =
-
-;Logging configuration
-[log]
-; Available loglevels: 10 = DEBUG (default) | 20 = INFO | 30 = WARNING | 40 = ERROR
-level =
-file = smfauth.log \ No newline at end of file
diff --git a/scripts/smfauth.py b/scripts/smfauth.py
deleted file mode 100644
index 2b1ee3aa8..000000000
--- a/scripts/smfauth.py
+++ /dev/null
@@ -1,707 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8
-
-#
-# smfauth.py - Sample script to demonstrate authentication against
-# an existing simple machine forums forum database.
-#
-# Requirements:
-# * python >=2.4 and the following python modules:
-# * ice-python
-# * PIL >=1.1.5 (only if avatar import is enabled)
-# * MySQLdb
-# * daemon (when run as a daemon)
-
-import sys
-import Ice
-import thread
-import logging
-import urllib2
-import ConfigParser
-import re
-
-from logging import (debug,
- info,
- warning,
- error,
- critical,
- getLogger)
-from optparse import OptionParser
-
-try:
- from hashlib import sha1
-except ImportError: # python 2.4 compat
- from sha import sha as sha1
-
-def x2bool(s):
- """Helper function to convert strings from the config to bool"""
- if isinstance(s, bool):
- return s
- elif isinstance(s, basestring):
- return s.lower() in ['1', 'true']
- raise ValueError()
-
-#
-#--- Default configuration values
-#
-cfgfile = 'smfauth.ini'
-user_texture_resolution = (600,60)
-default = {'database':(('lib', str, 'MySQLdb'),
- ('name', str, 'smf'),
- ('user', str, 'smf'),
- ('password', str, 'secret'),
- ('prefix', str, 'smf_'),
- ('host', str, '127.0.0.1'),
- ('port', int, 3306)),
- 'forum':(('path', str, 'http://localhost/smf/'),),
-
- 'user':(('id_offset', int, 1000000000),
- ('avatar_enable', x2bool, False),
- ('avatar_username_enable', x2bool, True),
- ('avatar_username_font', str, 'verdana.ttf'),
- ('avatar_username_fontsize', int, 30),
- ('avatar_username_x', int, 65),
- ('avatar_username_y', int, 10),
- ('avatar_username_fill', str, '#FF0000')),
-
- 'ice':(('host', str, '127.0.0.1'),
- ('port', int, 6502),
- ('slice', str, 'Murmur.ice')),
-
- 'iceraw':None,
-
- 'murmur':(('servers', lambda x:map(int, x.split(',')), []),),
- 'glacier':(('enabled', x2bool, False),
- ('user', str, 'smf'),
- ('password', str, 'secret'),
- ('host', str, 'localhost'),
- ('port', int, '4063')),
-
- 'log':(('level', int, logging.DEBUG),
- ('file', str, 'smfauth.log'))}
-
-#
-#--- Helpers
-#
-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.iteritems():
- if not v:
- # Output this whole section as a list of raw key/value tuples
- try:
- self.__dict__[h] = cfg.items(h)
- except ConfigParser.NoSectionError:
- self.__dict__[h] = []
- else:
- self.__dict__[h] = config()
- for name, conv, vdefault in v:
- try:
- self.__dict__[h].__dict__[name] = conv(cfg.get(h, name))
- except (ValueError, ConfigParser.NoSectionError, ConfigParser.NoOptionError):
- self.__dict__[h].__dict__[name] = vdefault
-
-def entity_decode(string):
- """
- Python reverse implementation of php htmlspecialchars
- """
- htmlspecialchars = (('"', '&quot;'),
- ("'", '&#039;'),
- ('<', '&lt;'),
- ('>', '&gt'),
- ('&', '&amp;'))
- ret = string
- for (s,t) in htmlspecialchars:
- ret = ret.replace(t, s)
- return ret
-
-def entity_encode(string):
- """
- Python implementation of htmlspecialchars
- """
- htmlspecialchars = (('&', '&amp;'),
- ('"', '&quot;'),
- ("'", '&#039;'),
- ('<', '&lt;'),
- ('>', '&gt'))
- ret = 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')
- except db.Error, 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):
- c = cls.cursor()
- try:
- c.execute(*args, **kwargs)
- except db.OperationalError, e:
- error('Database operational error %d: %s', e.args[0], e.args[1])
- c.close()
- cls.invalidate_connection()
- 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():
- #
- #--- Authenticator implementation
- # All of this has to go in here so we can correctly daemonize the tool
- # without loosing the file descriptors opened by the Ice module
- Ice.loadSlice(cfg.ice.slice)
- import Murmur
-
- class smfauthenticatorApp(Ice.Application):
- def run(self, args):
- self.shutdownOnInterrupt()
-
- if not self.initializeIceConnection():
- return 1
-
- # Serve till we are stopped
- self.communicator().waitForShutdown()
-
- if self.interrupted():
- warning('Caught interrupt, shutting down')
-
- threadDB.disconnect()
- return 0
-
- def initializeIceConnection(self):
- """
- Establishes the two-way Ice connection and adds the authenticator to the
- configured servers
- """
- ice = self.communicator()
-
- 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))
- try:
- meta = Murmur.MetaPrx.checkedCast(base)
- except Ice.LocalException, e:
- error('Could not connect to Ice server, error %d: %s', e.error, str(e).replace('\n', ' '))
- return False
-
- adapter = ice.createObjectAdapterWithEndpoints('Callback.Client', 'tcp -h %s' % cfg.ice.host)
- adapter.activate()
-
- for server in meta.getBootedServers():
- if not cfg.murmur.servers or server.id() in cfg.murmur.servers:
- info('Setting authenticator for server %d', server.id())
- authprx = adapter.addWithUUID(smfauthenticator(server, adapter))
- auth = Murmur.ServerUpdatingAuthenticatorPrx.uncheckedCast(authprx)
- server.setAuthenticator(auth)
- return True
-
- class smfauthenticator(Murmur.ServerUpdatingAuthenticator):
- texture_cache = {}
- def __init__(self, server, adapter):
- Murmur.ServerUpdatingAuthenticator.__init__(self)
- self.server = server
-
- if cfg.user.avatar_enable and cfg.user.avatar_username_enable:
- # Load font
- try:
- self.font = ImageFont.truetype(cfg.user.avatar_username_font, cfg.user.avatar_username_fontsize)
- except IOError, e:
- error("Could not load font for username texture overlay from '%s': %s", cfg.user.avatar_username_font, e)
- self.font = None
- else:
- self.font = None
-
-
- def authenticate(self, name, pw, certlist, certhash, strong, current = None):
- """
- This function is called to authenticate a user
- """
-
- # Search for the user in the database
- FALL_THROUGH = -2
- AUTH_REFUSED = -1
-
- if name == 'SuperUser':
- debug('Forced fall through for SuperUser')
- return (FALL_THROUGH, None, None)
- print entity_encode(name)
- try:
- sql = 'SELECT ID_MEMBER, passwd, ID_GROUP, memberName, realName, additionalGroups, is_activated FROM %smembers WHERE LOWER(memberName) = LOWER(%%s) OR realName = %%s' % cfg.database.prefix
- cur = threadDB.execute(sql, (name, entity_encode(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, ug, unm, urn, uag, activated = res
-
- if activated == 1 and smf_check_hash(pw, upw, unm):
- # Authenticated, fetch group memberships
- try:
- sql = 'SELECT groupName FROM %smembergroups WHERE ID_GROUP IN (%s)' % (cfg.database.prefix, str(ug) if not uag else str(ug)+','+uag)
- cur = threadDB.execute(sql)
- except threadDbException:
- return (FALL_THROUGH, None, None)
-
- res = cur.fetchall()
- cur.close()
- if res:
- res = [a[0] for a in res]
-
- info('User authenticated: "%s" (%d)', name, uid + cfg.user.id_offset)
- debug('Group memberships: %s', str(res))
- return (uid + cfg.user.id_offset, entity_decode(urn), res)
-
- info('Failed authentication attempt for user: "%s" (%d)', name, uid + cfg.user.id_offset)
- return (AUTH_REFUSED, None, None)
-
-
- def getInfo(self, id, current = None):
- """
- Gets called to fetch user specific information
- """
-
- # We do not expose any additional information so always fall through
- debug('getInfo for %d -> denied', id)
- return (False, None)
-
-
- def nameToId(self, name, current = None):
- """
- Gets called to get the id for a given username
- """
-
- FALL_THROUGH = -2
- if name == 'SuperUser':
- debug('nameToId SuperUser -> forced fall through')
- return FALL_THROUGH
-
- try:
- sql = 'SELECT ID_MEMBER FROM %smembers WHERE LOWER(memberName) = 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
-
-
- def idToName(self, id, current = None):
- """
- Gets called to get the username for a given id
- """
-
- FALL_THROUGH = ""
- # Make sure the ID is in our range and transform it to the actual smf user id
- if id < cfg.user.id_offset:
- return FALL_THROUGH
- bbid = id - cfg.user.id_offset
-
- # Fetch the user from the database
- try:
- sql = 'SELECT memberName 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
-
-
- def idToTexture(self, id, current = None):
- """
- Gets called to get the corresponding texture for a user
- """
-
- FALL_THROUGH = ""
-
- debug('idToTexture for %d', id)
- if id < cfg.user.id_offset or not cfg.user.avatar_enable:
- 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 realName, 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
- username, avatar = res
-
- if not avatar:
- # Either the user has none or it is in the attachments, check there
- try:
- sql = 'SELECT ID_ATTACH, file_hash FROM %sattachments 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:
- # 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
-
- if cfg.forum.path.startswith('file://'):
- # We are supposed to load this from the local fs
- avatar_file = cfg.forum.path + 'attachments/%d_%s' % (res[0], res[1])
- else:
- avatar_file = cfg.forum.path + 'index.php?action=dlattach;attach=%d;type=avatar' % res[0]
- elif "://" in avatar:
- # ...or it is a external link
- avatar_file = avatar
- else:
- # Or it is saved locally in the avatar folder
- avatar_file = cfg.forum.path + 'avatars/' + avatar
-
- if avatar_file in self.texture_cache:
- return self.texture_cache[avatar_file]
-
- try:
- handle = urllib2.urlopen(avatar_file)
- file = StringIO.StringIO(handle.read())
- handle.close()
- except urllib2.URLError, e:
- warning('Image download for "%s" (%d) failed: %s', avatar_file, id, str(e))
- return FALL_THROUGH
-
- try:
- # Load image and scale it
- img = Image.open(file).convert("RGBA")
- img.thumbnail((user_texture_resolution[0],user_texture_resolution[1]), Image.ANTIALIAS)
- img = img.transform(user_texture_resolution,
- Image.EXTENT,
- (0, 0, user_texture_resolution[0], user_texture_resolution[1]))
-
- if cfg.user.avatar_username_enable and self.font:
- # Insert user name into picture
- draw = ImageDraw.Draw(img)
- draw.text((cfg.user.avatar_username_x, cfg.user.avatar_username_y),
- entity_decode(username),
- fill = cfg.user.avatar_username_fill,
- font = self.font)
-
- r,g,b,a = img.split()
- raw = Image.merge('RGBA', (b, g, r, a)).tostring()
- comp = compress(raw)
- res = pack('>L', len(raw)) + comp
- except Exception, e:
- warning('Image manipulation for "%s" (%d) failed', avatar_file, id)
- debug(e)
- return FALL_THROUGH
-
- self.texture_cache[avatar_file] = res
- return res
-
-
- def registerUser(self, name, current = None):
- """
- Gets called when the server is asked to register a user.
- """
-
- FALL_THROUGH = -2
- debug('registerUser "%s" -> fall through', name)
- return FALL_THROUGH
-
-
- def unregisterUser(self, id, current = None):
- """
- Gets called when the server is asked to unregister a user.
- """
-
- FALL_THROUGH = -1
- # Return -1 to fall through to internal server database, we will not modify the smf database
- # but we can make murmur delete all additional information it got this way.
- debug('unregisterUser %d -> fall through', id)
- return FALL_THROUGH
-
-
- 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, memberName FROM %smembers WHERE is_activated = 1 AND memberName 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])
-
-
- def setInfo(self, id, info, current = None):
- """
- Gets called when the server is supposed to save additional information
- about a user to his database
- """
-
- FALL_THROUGH = -1
- # Return -1 to fall through to the internal server handler. We must not modify
- # the smf database so the additional information is stored in murmurs database
- debug('setInfo %d -> fall through', id)
- return FALL_THROUGH
-
-
- def setTexture(self, id, texture, current = None):
- """
- Gets called when the server is asked to update the user texture of a user
- """
-
- FAILED = 0
- FALL_THROUGH = -1
-
- if id < cfg.user.id_offset:
- debug('setTexture %d -> fall through', id)
- return FALL_THROUGH
-
- if cfg.user.avatar_enable:
- # Report a fail (0) as we will not update the avatar in the smf database.
- debug('setTexture %d -> failed', id)
- return FAILED
-
- # If we don't use textures from smf we let mumble save it
- debug('setTexture %d -> fall through', id)
- return FALL_THROUGH
-
-
- class CustomLogger(Ice.Logger):
- """
- Logger implementation to pipe Ice log messages into
- out own log
- """
-
- def __init__(self):
- Ice.Logger.__init__(self)
- self._log = getLogger("Ice")
-
- def _print(self, message):
- self._log.info(message)
-
- def trace(self, category, message):
- self._log.debug("Trace %s: %s", category, message)
-
- def warning(self, message):
- self._log.warning(message)
-
- def error(self, message):
- self._log.error(message)
-
- #
- #--- Start of authenticator
- #
- info('Starting smf mumble authenticator')
- initdata = Ice.InitializationData()
- initdata.properties = Ice.createProperties([], initdata.properties)
- for prop, val in cfg.iceraw:
- initdata.properties.setProperty(prop, val)
- initdata.logger = CustomLogger()
-
- app = smfauthenticatorApp()
- 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
- """
- return sha1(username.lower() + password).hexdigest() == hash
-
-#
-#--- Start of program
-#
-if __name__ == '__main__':
- # Parse commandline options
- parser = OptionParser()
- parser.add_option('-i', '--ini',
- help = 'load configuration from INI', default = cfgfile)
- parser.add_option('-v', '--verbose', action='store_true', dest = 'verbose',
- help = 'verbose output [default]', default = True)
- parser.add_option('-q', '--quiet', action='store_false', dest = 'verbose',
- help = 'only error output')
- parser.add_option('-d', '--daemon', action='store_true', dest = 'force_daemon',
- help = 'run as daemon', default = False)
- parser.add_option('-a', '--app', action='store_true', dest = 'force_app',
- help = 'do not run as daemon', default = False)
- (option, args) = parser.parse_args()
-
- if option.force_daemon and option.force_app:
- parser.print_help()
- sys.exit(1)
-
- # Load configuration
- try:
- cfg = config(option.ini, default)
- except Exception, e:
- print>>sys.stderr, 'Fatal error, could not load config file from "%s"' % cfgfile
- sys.exit(1)
-
- # Do conditional imports
- if cfg.user.avatar_enable:
- # If we use avatars we need PIL to manipulate it and some other stuff for working with them
- try:
- import Image
- if cfg.user.avatar_username_enable:
- import ImageFont
- import ImageDraw
- except ImportError, e:
- print>>sys.stderr, 'Error, could not import PIL library, '\
- 'please install the missing dependency and restart the authenticator'
- sys.exit(1)
-
- import StringIO
-
- from zlib import compress
- from struct import pack
-
- try:
- db = __import__(cfg.database.lib)
- except ImportError, e:
- print>>sys.stderr, 'Fatal error, could not import database library "%s", '\
- 'please install the missing dependency and restart the authenticator' % cfg.database.lib
- sys.exit(1)
-
-
- # Initialize logger
- if cfg.log.file:
- try:
- logfile = open(cfg.log.file, 'a')
- except IOError, e:
- print>>sys.stderr, 'Fatal error, could not open logfile "%s"' % cfg.log.file
- sys.exit(1)
- else:
- logfile = logging.sys.stderr
-
-
- if option.verbose:
- level = cfg.log.level
- else:
- level = logging.ERROR
-
- logging.basicConfig(level = level,
- format='%(asctime)s %(levelname)s %(message)s',
- stream = logfile)
-
- # As the default try to run as daemon. Silently degrade to running as a normal application if this fails
- # unless the user explicitly defined what he expected with the -a / -d parameter.
- try:
- if option.force_app:
- raise ImportError # Pretend that we couldn't import the daemon lib
- import daemon
- except ImportError:
- if option.force_daemon:
- print>>sys.stderr, 'Fatal error, could not daemonize process due to missing "daemon" library, ' \
- 'please install the missing dependency and restart the authenticator'
- sys.exit(1)
- do_main_program()
- else:
- context = daemon.DaemonContext(working_directory = sys.path[0],
- stderr = logfile)
- context.__enter__()
- try:
- do_main_program()
- finally:
- context.__exit__(None, None, None)