mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2025-07-14 15:00:16 +02:00
ICE Authenticator for Murmur
- reads username, pwhash from db table containing MumbleUser models - needs to be started separately somehow, instructions unclear - adapted from https://github.com/mumble-voip/mumble-scripts/blob/master/Authenticators/SMF/2.0/smfauth.py Closes #165
This commit is contained in:
parent
a0955c054a
commit
8c4015126c
@ -97,15 +97,6 @@ DATABASES = {
|
||||
'PASSWORD': os.environ.get('AA_DB_PHPBB3_PASSWORD', 'password'),
|
||||
'HOST': os.environ.get('AA_DB_PHPBB3_HOST', '127.0.0.1'),
|
||||
'PORT': os.environ.get('AA_DB_PHPBB3_PORT', '3306'),
|
||||
},
|
||||
|
||||
'mumble': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_mumble',
|
||||
'USER': os.environ.get('AA_DB_MUMBLE_USER', 'allianceserver'),
|
||||
'PASSWORD': os.environ.get('AA_DB_MUMBLE_PASSWORD', 'password'),
|
||||
'HOST': os.environ.get('AA_DB_MUMBLE_HOST', '127.0.0.1'),
|
||||
'PORT': os.environ.get('AA_DB_MUMBLE_PORT', '3306'),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,46 +6,13 @@ import django
|
||||
from django.db import connections
|
||||
from django.conf import settings
|
||||
|
||||
from services.models import MumbleUser
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class MumbleManager:
|
||||
SQL_SELECT_USER_MAX_ID = r"SELECT max(user_id)+1 as next_id from murmur_users"
|
||||
|
||||
SQL_SELECT_GROUP_MAX_ID = r"SELECT MAX(group_id)+1 FROM murmur_groups"
|
||||
|
||||
SQL_CREATE_USER = r"INSERT INTO murmur_users (server_id, user_id, name, pw) VALUES (%s, %s, %s, %s)"
|
||||
|
||||
SQL_SELECT_GET_USER_ID_BY_NAME = r"SELECT user_id from murmur_users WHERE name = %s AND server_id = %s"
|
||||
|
||||
SQL_CHECK_USER_EXIST = r"SELECT name from murmur_users WHERE name = %s AND server_id = %s"
|
||||
|
||||
SQL_DELETE_USER = r"DELETE FROM murmur_users WHERE name = %s AND server_id = %s"
|
||||
|
||||
SQL_UPDATE_USER_PASSWORD = r"UPDATE murmur_users SET pw = %s WHERE name = %s AND server_id = %s"
|
||||
|
||||
SQL_GET_GROUPS = r"SELECT group_id, name FROM murmur_groups WHERE server_id = %s AND channel_id = 0"
|
||||
|
||||
SQL_GET_GROUP_FROM_NAME = r"SELECT group_id, name FROM murmur_groups " \
|
||||
r"WHERE server_id = %s AND channel_id = 0 AND name = %s"
|
||||
|
||||
SQL_GET_USER_GROUPS = r"SELECT murmur_groups.name FROM murmur_groups, murmur_group_members WHERE " \
|
||||
r"murmur_group_members.group_id = murmur_groups.group_id AND " \
|
||||
r"murmur_group_members.server_id = murmur_groups.server_id AND " \
|
||||
r"murmur_group_members.user_id = %s"
|
||||
|
||||
SQL_ADD_GROUP = r"INSERT INTO murmur_groups (group_id, server_id, name, channel_id, inherit, inheritable) " \
|
||||
r"VALUES (%s, %s, %s, 0, 1, 1)"
|
||||
|
||||
SQL_ADD_USER_TO_GROUP = r"INSERT INTO murmur_group_members (group_id, server_id, user_id, addit) " \
|
||||
r"VALUES (%s, %s, %s, 1)"
|
||||
|
||||
SQL_DELETE_USER_FROM_GROUP = r"DELETE FROM murmur_group_members WHERE group_id = %s " \
|
||||
r"AND server_id = %s AND user_id = %s"
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def __santatize_username(username):
|
||||
@ -69,167 +36,66 @@ class MumbleManager:
|
||||
def _gen_pwhash(password):
|
||||
return hashlib.sha1(password).hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def _get_groups():
|
||||
dbcursor = connections['mumble'].cursor()
|
||||
dbcursor.execute(MumbleManager.SQL_GET_GROUPS, [settings.MUMBLE_SERVER_ID])
|
||||
rows = dbcursor.fetchall()
|
||||
|
||||
out = {}
|
||||
for row in rows:
|
||||
out[row[1]] = row[0]
|
||||
|
||||
logger.debug("Got mumble groups %s" % out)
|
||||
return out
|
||||
|
||||
@staticmethod
|
||||
def _get_group(name):
|
||||
logger.debug("Looking for group name %s in mumble." % name)
|
||||
dbcursor = connections['mumble'].cursor()
|
||||
dbcursor.execute(MumbleManager.SQL_GET_GROUP_FROM_NAME, [settings.MUMBLE_SERVER_ID, name])
|
||||
row = dbcursor.fetchone()
|
||||
if row:
|
||||
logger.debug("Found group %s in mumble - %s" % (name, row[0]))
|
||||
return row[0]
|
||||
|
||||
@staticmethod
|
||||
def _get_user_groups(name):
|
||||
logger.debug("Getting mumble groups for username %s" % name)
|
||||
dbcursor = connections['mumble'].cursor()
|
||||
user_id = MumbleManager.get_user_id_by_name(name)
|
||||
dbcursor.execute(MumbleManager.SQL_GET_USER_GROUPS, [user_id])
|
||||
out = [row[0] for row in dbcursor.fetchall()]
|
||||
logger.debug("Got user %s mumble groups %s" % (name, out))
|
||||
return out
|
||||
|
||||
@staticmethod
|
||||
def _add_group(name):
|
||||
logger.debug("Adding group %s to mumble server." % name)
|
||||
dbcursor = connections['mumble'].cursor()
|
||||
dbcursor.execute(MumbleManager.SQL_SELECT_GROUP_MAX_ID)
|
||||
row = dbcursor.fetchone()
|
||||
groupid = row[0]
|
||||
dbcursor.execute(MumbleManager.SQL_ADD_GROUP, [groupid, settings.MUMBLE_SERVER_ID, name])
|
||||
logger.info("Created group %s on mumble server with id %s" % (name, groupid))
|
||||
return groupid
|
||||
|
||||
@staticmethod
|
||||
def _add_user_to_group(userid, groupid):
|
||||
if userid != None:
|
||||
dbcursor = connections['mumble'].cursor()
|
||||
dbcursor.execute(MumbleManager.SQL_ADD_USER_TO_GROUP, [groupid, settings.MUMBLE_SERVER_ID, userid])
|
||||
logger.info("Added user id %s to mumble group id %s" % (userid, groupid))
|
||||
|
||||
@staticmethod
|
||||
def _del_user_from_group(userid, groupid):
|
||||
dbcursor = connections['mumble'].cursor()
|
||||
dbcursor.execute(MumbleManager.SQL_DELETE_USER_FROM_GROUP, [groupid, settings.MUMBLE_SERVER_ID, userid])
|
||||
logger.info("Removed user id %s from mumble group id %s" % (userid, groupid))
|
||||
|
||||
@staticmethod
|
||||
def get_user_id_by_name(name):
|
||||
logger.debug("Getting mumble user id for user with name %s" % name)
|
||||
dbcursor = connections['mumble'].cursor()
|
||||
dbcursor.execute(MumbleManager.SQL_SELECT_GET_USER_ID_BY_NAME, [name, settings.MUMBLE_SERVER_ID])
|
||||
row = dbcursor.fetchone()
|
||||
if row:
|
||||
logger.debug("Got mumble user id %s for name %s" % (row[0], name))
|
||||
return row[0]
|
||||
|
||||
@staticmethod
|
||||
def create_user(corp_ticker, username):
|
||||
logger.debug("Creating mumble user with username %s and ticker %s" % (username, corp_ticker))
|
||||
dbcursor = connections['mumble'].cursor()
|
||||
username_clean = MumbleManager.__santatize_username(MumbleManager.__generate_username(username, corp_ticker))
|
||||
password = MumbleManager.__generate_random_pass()
|
||||
pwhash = MumbleManager._gen_pwhash(password)
|
||||
logger.debug("Proceeding with mumble user creation: clean username %s, pwhash starts with %s" % (username_clean, pwhash[0:5]))
|
||||
try:
|
||||
dbcursor.execute(MumbleManager.SQL_SELECT_USER_MAX_ID)
|
||||
user_id = dbcursor.fetchone()[0]
|
||||
|
||||
dbcursor.execute(MumbleManager.SQL_CREATE_USER,
|
||||
[settings.MUMBLE_SERVER_ID, user_id, username_clean, pwhash])
|
||||
logger.info("Added user to mumble with username %s" % username_clean)
|
||||
if MumbleUser.objects.filter(username=username_clean).exists() is False:
|
||||
logger.info("Creating mumble user %s" % username_clean)
|
||||
model = MumbleUser.objects.create(username=username_clean, pwhash=pwhash)
|
||||
return username_clean, password
|
||||
else:
|
||||
logger.warn("Mumble user %s already exists. Updating password")
|
||||
model = MumbleUser.objects.get(username=username_clean)
|
||||
model.pwhash = pwhash
|
||||
model.save()
|
||||
logger.info("Updated mumble user %s" % username_clean)
|
||||
return username_clean, password
|
||||
except django.db.utils.IntegrityError as error:
|
||||
logger.exception("IntegrityError during mumble create_user occured.")
|
||||
except:
|
||||
logger.exception("Unhandled exception occured.")
|
||||
logger.error("Exception prevented creation of mumble user. Returning blank for username, password.")
|
||||
return "", ""
|
||||
|
||||
@staticmethod
|
||||
def create_blue_user(corp_ticker, username):
|
||||
logger.debug("Creating mumble blue user with username %s and ticker %s" % (username, corp_ticker))
|
||||
dbcursor = connections['mumble'].cursor()
|
||||
username_clean = MumbleManager.__santatize_username(MumbleManager.__generate_username_blue(username,
|
||||
corp_ticker))
|
||||
username_clean = MumbleManager.__santatize_username(MumbleManager.__generate_username_blue(username, corp_ticker))
|
||||
password = MumbleManager.__generate_random_pass()
|
||||
pwhash = MumbleManager._gen_pwhash(password)
|
||||
logger.debug("Proceeding with mumble user creation: clean username %s, pwhash starts with %s" % (username_clean, pwhash[0:5]))
|
||||
try:
|
||||
dbcursor.execute(MumbleManager.SQL_SELECT_USER_MAX_ID)
|
||||
user_id = dbcursor.fetchone()[0]
|
||||
|
||||
dbcursor.execute(MumbleManager.SQL_CREATE_USER,
|
||||
[settings.MUMBLE_SERVER_ID, user_id, username_clean, pwhash])
|
||||
logger.info("Added blue user to mumble with username %s" % username_clean)
|
||||
if MumbleUser.objects.filter(username=username_clean).exists() is False:
|
||||
logger.info("Creating mumble user %s" % username_clean)
|
||||
model = MumbleUser.objects.create(username=username_clean, pwhash=pwhash)
|
||||
return username_clean, password
|
||||
else:
|
||||
logger.warn("Mumble user %s already exists. Updating password")
|
||||
model = MumbleUser.objects.get(username=username_clean)
|
||||
model.pwhash = pwhash
|
||||
model.save()
|
||||
logger.info("Updated mumble user %s" % username_clean)
|
||||
return username_clean, password
|
||||
except:
|
||||
logger.exception("Unhandled exception occured.")
|
||||
logger.error("Exception prevented creation of mumble blue user. Returning blank for username, password.")
|
||||
return "", ""
|
||||
|
||||
@staticmethod
|
||||
def check_user_exist(username):
|
||||
logger.debug("Checking if username %s exists on mumble." % username)
|
||||
dbcursor = connections['mumble'].cursor()
|
||||
dbcursor.execute(MumbleManager.SQL_CHECK_USER_EXIST,
|
||||
[username, settings.MUMBLE_SERVER_ID])
|
||||
|
||||
row = dbcursor.fetchone()
|
||||
if row and row[0].lower() == username.lower():
|
||||
logger.debug("Found username %s on mumble." % username)
|
||||
return True
|
||||
logger.debug("Unable to find username %s on mumble." % username)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def delete_user(username):
|
||||
logger.debug("Deleting user %s from mumble." % username)
|
||||
dbcursor = connections['mumble'].cursor()
|
||||
if MumbleManager.check_user_exist(username):
|
||||
try:
|
||||
|
||||
dbcursor.execute(MumbleManager.SQL_DELETE_USER,
|
||||
[username, settings.MUMBLE_SERVER_ID])
|
||||
logger.info("Deleted user %s from mumble." % username)
|
||||
return True
|
||||
except:
|
||||
logger.exception("Exception prevented deletion of user %s from mumble." % username)
|
||||
return False
|
||||
logger.error("User %s not found on mumble. Unable to delete." % username)
|
||||
if MumbleUser.objects.filter(username=username).exists():
|
||||
MumbleUser.objects.filter(username=username).delete()
|
||||
logger.info("Deleted user %s from mumble" % username)
|
||||
return True
|
||||
logger.error("Unable to delete user %s from mumble: MumbleUser model not found" % username)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def update_user_password(username, password=None):
|
||||
logger.debug("Updating mumble user %s password." % username)
|
||||
dbcursor = connections['mumble'].cursor()
|
||||
if not password:
|
||||
password = MumbleManager.__generate_random_pass()
|
||||
pwhash = MumbleManager._gen_pwhash(password)
|
||||
logger.debug("Proceeding with mumble user %s password update - pwhash starts with %s" % (username, pwhash[0:5]))
|
||||
if MumbleManager.check_user_exist(username):
|
||||
try:
|
||||
|
||||
dbcursor.execute(MumbleManager.SQL_UPDATE_USER_PASSWORD,
|
||||
[pwhash, username, settings.MUMBLE_SERVER_ID])
|
||||
logger.info("Updated mumble user %s password." % username)
|
||||
return password
|
||||
except:
|
||||
logger.exception("Exception prevented updating of mumble user %s password." % username)
|
||||
return ""
|
||||
if MumbleUser.objects.filter(username=username).exists():
|
||||
model = MumbleUser.objects.get(username=username)
|
||||
model.pwhash = pwhash
|
||||
model.save()
|
||||
return password
|
||||
logger.error("User %s not found on mumble. Unable to update password." % username)
|
||||
return ""
|
||||
|
||||
@ -237,24 +103,11 @@ class MumbleManager:
|
||||
def update_groups(username, groups):
|
||||
logger.debug("Updating mumble user %s groups %s" % (username, groups))
|
||||
userid = MumbleManager.get_user_id_by_name(username)
|
||||
mumble_groups = MumbleManager._get_groups()
|
||||
user_groups = set(MumbleManager._get_user_groups(username))
|
||||
act_groups = set([g.replace(' ', '-') for g in groups])
|
||||
addgroups = act_groups - user_groups
|
||||
remgroups = user_groups - act_groups
|
||||
logger.info("Updating mumble user %s groups - adding %s, removing %s" % (username, addgroups, remgroups))
|
||||
for g in addgroups:
|
||||
if not g in mumble_groups:
|
||||
mumble_groups[g] = MumbleManager._add_group(g)
|
||||
try:
|
||||
logger.debug("Adding mumble user %s to group %s" % (userid, mumble_groups[g]))
|
||||
MumbleManager._add_user_to_group(userid, mumble_groups[g])
|
||||
except:
|
||||
logger.exception("Exception occured while adding mumble user %s with id %s to group %s with id %s" % (username, userid, g, mumble_groups[g]))
|
||||
|
||||
for g in remgroups:
|
||||
try:
|
||||
logger.debug("Deleting mumble user %s from group %s" % (userid, mumble_groups[g]))
|
||||
MumbleManager._del_user_from_group(userid, mumble_groups[g])
|
||||
except:
|
||||
logger.exception("Exception occured while removing mumble user %s with id %s from group %s with id %s" % (username, userid, g, mumble_groups[g]))
|
||||
safe_groups = set([g.replace(' ', '-') for g in groups])
|
||||
if MumbleUser.objects.filter(username=username).exists():
|
||||
logger.info("Updating mumble user %s groups to" % (username, safe_groups))
|
||||
model = MumbleUser.objects.get(username=username)
|
||||
model.groups = safe_groups
|
||||
model.save()
|
||||
else:
|
||||
logger.error("User %s not found on mumble. Unable to update groups." % username)
|
||||
|
@ -38,3 +38,11 @@ class DiscordAuthToken(models.Model):
|
||||
def __str__(self):
|
||||
output = "Discord Token for email %s user %s" % (self.email, self.user)
|
||||
return output.encode('utf-8')
|
||||
|
||||
class MumbleUser(models.Model):
|
||||
username = models.CharField(max_length=254, unique=True)
|
||||
pwhash = models.CharField(max_length=40)
|
||||
groups = models.TextField(blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.username + ' - ' + str(self.user)
|
||||
|
40
thirdparty/Mumble/authenticator.ini
vendored
Normal file
40
thirdparty/Mumble/authenticator.ini
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
;Database configuration
|
||||
[database]
|
||||
;Only tested with MySQL at the moment
|
||||
lib = MySQLdb
|
||||
name = alliance_auth
|
||||
user = allianceserver
|
||||
password = password
|
||||
prefix =
|
||||
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
|
||||
avatar_enable = False
|
||||
;Reject users if the authenticator experiences an internal error during authentication
|
||||
reject_on_error = True
|
||||
|
||||
;Ice configuration
|
||||
[ice]
|
||||
host = 127.0.0.1
|
||||
port = 6502
|
||||
slice = Murmur.ice
|
||||
secret =
|
||||
watchdog = 30
|
||||
|
||||
;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 = allianceauth.log
|
||||
|
||||
[iceraw]
|
||||
Ice.ThreadPool.Server.Size = 5
|
796
thirdparty/Mumble/authenticator.py
vendored
Normal file
796
thirdparty/Mumble/authenticator.py
vendored
Normal file
@ -0,0 +1,796 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
# All rights reserved.
|
||||
# Adapted by Adarnof for AllianceAuth
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
|
||||
# - Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# - Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# - Neither the name of the Mumble Developers nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from this
|
||||
# software without specific prior written permission.
|
||||
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# `AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#
|
||||
# allianceauth.py - Authenticator implementation for password authenticating
|
||||
# a Murmur server against an AllianceAuth database
|
||||
#
|
||||
# Requirements:
|
||||
# * python >=2.4 and the following python modules:
|
||||
# * ice-python
|
||||
# * MySQLdb
|
||||
# * daemon (when run as a daemon)
|
||||
#
|
||||
|
||||
import sys
|
||||
import Ice
|
||||
import thread
|
||||
import urllib2
|
||||
import logging
|
||||
import ConfigParser
|
||||
|
||||
from threading import Timer
|
||||
from optparse import OptionParser
|
||||
from logging import (debug,
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
critical,
|
||||
exception,
|
||||
getLogger)
|
||||
|
||||
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 = 'allianceauth.ini'
|
||||
default = {'database':(('lib', str, 'MySQLdb'),
|
||||
('name', str, 'alliance_auth'),
|
||||
('user', str, 'allianceserver'),
|
||||
('password', str, 'password'),
|
||||
('prefix', str, ''),
|
||||
('host', str, '127.0.0.1'),
|
||||
('port', int, 3306)),
|
||||
|
||||
'user':(('id_offset', int, 1000000000),
|
||||
('reject_on_error', x2bool, True)),
|
||||
|
||||
'ice':(('host', str, '127.0.0.1'),
|
||||
('port', int, 6502),
|
||||
('slice', str, 'Murmur.ice'),
|
||||
('secret', str, ''),
|
||||
('watchdog', int, 30)),
|
||||
|
||||
'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, 'allianceauth.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
|
||||
|
||||
def entity_decode(string):
|
||||
"""
|
||||
Python reverse implementation of php htmlspecialchars
|
||||
"""
|
||||
htmlspecialchars = (('"', '"'),
|
||||
("'", '''),
|
||||
('<', '<'),
|
||||
('>', '>'),
|
||||
('&', '&'))
|
||||
ret = string
|
||||
for (s,t) in htmlspecialchars:
|
||||
ret = ret.replace(t, s)
|
||||
return ret
|
||||
|
||||
def entity_encode(string):
|
||||
"""
|
||||
Python implementation of htmlspecialchars
|
||||
"""
|
||||
htmlspecialchars = (('&', '&'),
|
||||
('"', '"'),
|
||||
("'", '''),
|
||||
('<', '<'),
|
||||
('>', '>'))
|
||||
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')
|
||||
# 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, 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, 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():
|
||||
#
|
||||
#--- 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
|
||||
slicedir = Ice.getSliceDir()
|
||||
if not slicedir:
|
||||
slicedir = ["-I/usr/share/Ice/slice", "-I/usr/share/slice"]
|
||||
else:
|
||||
slicedir = ['-I' + slicedir]
|
||||
Ice.loadSlice('', slicedir + [cfg.ice.slice])
|
||||
import Murmur
|
||||
|
||||
class allianceauthauthenticatorApp(Ice.Application):
|
||||
def run(self, args):
|
||||
self.shutdownOnInterrupt()
|
||||
|
||||
if not self.initializeIceConnection():
|
||||
return 1
|
||||
|
||||
if cfg.ice.watchdog > 0:
|
||||
self.failedWatch = True
|
||||
self.checkConnection()
|
||||
|
||||
# Serve till we are stopped
|
||||
self.communicator().waitForShutdown()
|
||||
self.watchdog.cancel()
|
||||
|
||||
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.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')
|
||||
|
||||
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 = Murmur.MetaPrx.uncheckedCast(base)
|
||||
|
||||
adapter = ice.createObjectAdapterWithEndpoints('Callback.Client', 'tcp -h %s' % cfg.ice.host)
|
||||
adapter.activate()
|
||||
|
||||
metacbprx = adapter.addWithUUID(metaCallback(self))
|
||||
self.metacb = Murmur.MetaCallbackPrx.uncheckedCast(metacbprx)
|
||||
|
||||
authprx = adapter.addWithUUID(smfauthenticator())
|
||||
self.auth = Murmur.ServerUpdatingAuthenticatorPrx.uncheckedCast(authprx)
|
||||
|
||||
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')
|
||||
|
||||
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())
|
||||
server.setAuthenticator(self.auth)
|
||||
|
||||
except (Murmur.InvalidSecretException, Ice.UnknownUserException, Ice.ConnectionRefusedException), e:
|
||||
if isinstance(e, Ice.ConnectionRefusedException):
|
||||
error('Server refused connection')
|
||||
elif isinstance(e, Murmur.InvalidSecretException) or \
|
||||
isinstance(e, Ice.UnknownUserException) and (e.unknown == 'Murmur::InvalidSecretException'):
|
||||
error('Invalid ice secret')
|
||||
else:
|
||||
# We do not actually want to handle this one, re-raise it
|
||||
raise 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
|
||||
except Ice.Exception, e:
|
||||
error('Failed connection check, will retry in next watchdog run (%ds)', cfg.ice.watchdog)
|
||||
debug(str(e))
|
||||
self.failedWatch = True
|
||||
|
||||
# Renew the timer
|
||||
self.watchdog = Timer(cfg.ice.watchdog, self.checkConnection)
|
||||
self.watchdog.start()
|
||||
|
||||
def checkSecret(func):
|
||||
"""
|
||||
Decorator that checks whether the server transmitted the right secret
|
||||
if a secret is supposed to be used.
|
||||
"""
|
||||
if not cfg.ice.secret:
|
||||
return func
|
||||
|
||||
def newfunc(*args, **kws):
|
||||
if 'current' in kws:
|
||||
current = kws["current"]
|
||||
else:
|
||||
current = args[-1]
|
||||
|
||||
if not current or 'secret' not in current.ctx or current.ctx['secret'] != cfg.ice.secret:
|
||||
error('Server transmitted invalid secret. Possible injection attempt.')
|
||||
raise Murmur.InvalidSecretException()
|
||||
|
||||
return func(*args, **kws)
|
||||
|
||||
return newfunc
|
||||
|
||||
def fortifyIceFu(retval = None, exceptions = (Ice.Exception,)):
|
||||
"""
|
||||
Decorator that catches exceptions,logs them and returns a safe retval
|
||||
value. This helps preventing the authenticator getting stuck in
|
||||
critical code paths. Only exceptions that are instances of classes
|
||||
given in the exceptions list are not caught.
|
||||
|
||||
The default is to catch all non-Ice exceptions.
|
||||
"""
|
||||
def newdec(func):
|
||||
def newfunc(*args, **kws):
|
||||
try:
|
||||
return func(*args, **kws)
|
||||
except Exception, e:
|
||||
catch = True
|
||||
for ex in exceptions:
|
||||
if isinstance(e, ex):
|
||||
catch = False
|
||||
break
|
||||
|
||||
if catch:
|
||||
critical('Unexpected exception caught')
|
||||
exception(e)
|
||||
return retval
|
||||
raise
|
||||
|
||||
return newfunc
|
||||
return newdec
|
||||
|
||||
class metaCallback(Murmur.MetaCallback):
|
||||
def __init__(self, app):
|
||||
Murmur.MetaCallback.__init__(self)
|
||||
self.app = app
|
||||
|
||||
@fortifyIceFu()
|
||||
@checkSecret
|
||||
def started(self, server, current = None):
|
||||
"""
|
||||
This function is called when a virtual server is started
|
||||
and makes sure an authenticator gets attached if needed.
|
||||
"""
|
||||
if not cfg.murmur.servers or server.id() in cfg.murmur.servers:
|
||||
info('Setting authenticator for virtual server %d', server.id())
|
||||
try:
|
||||
server.setAuthenticator(app.auth)
|
||||
# Apparently this server was restarted without us noticing
|
||||
except (Murmur.InvalidSecretException, Ice.UnknownUserException), e:
|
||||
if hasattr(e, "unknown") and e.unknown != "Murmur::InvalidSecretException":
|
||||
# Special handling for Murmur 1.2.2 servers with invalid slice files
|
||||
raise e
|
||||
|
||||
error('Invalid ice secret')
|
||||
return
|
||||
else:
|
||||
debug('Virtual server %d got started', server.id())
|
||||
|
||||
@fortifyIceFu()
|
||||
@checkSecret
|
||||
def stopped(self, server, current = None):
|
||||
"""
|
||||
This function is called when a virtual server is stopped
|
||||
"""
|
||||
if self.app.connected:
|
||||
# Only try to output the server id if we think we are still connected to prevent
|
||||
# flooding of our thread pool
|
||||
try:
|
||||
if not cfg.murmur.servers or server.id() in cfg.murmur.servers:
|
||||
info('Authenticated virtual server %d got stopped', server.id())
|
||||
else:
|
||||
debug('Virtual server %d got stopped', server.id())
|
||||
return
|
||||
except Ice.ConnectionRefusedException:
|
||||
self.app.connected = False
|
||||
|
||||
debug('Server shutdown stopped a virtual server')
|
||||
|
||||
if cfg.user.reject_on_error: # Python 2.4 compat
|
||||
authenticateFortifyResult = (-1, None, None)
|
||||
else:
|
||||
authenticateFortifyResult = (-2, None, None)
|
||||
|
||||
class allianceauthauthenticator(Murmur.ServerUpdatingAuthenticator):
|
||||
texture_cache = {}
|
||||
def __init__(self):
|
||||
Murmur.ServerUpdatingAuthenticator.__init__(self)
|
||||
|
||||
@fortifyIceFu(authenticateFortifyResult)
|
||||
@checkSecret
|
||||
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 id, pwhash, groups FROM %sservices_mumbleuser WHERE username = %%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, upwhash, ugroups = res
|
||||
|
||||
if allianceauth_check_hash(pw, upwhash):
|
||||
info('User authenticated: "%s" (%d)', name, uid + cfg.user.id_offset)
|
||||
debug('Group memberships: %s', str(ugroups))
|
||||
return (uid + cfg.user.id_offset, entity_decode(name), ugroups)
|
||||
|
||||
info('Failed authentication attempt for user: "%s" (%d)', name, uid + cfg.user.id_offset)
|
||||
return (AUTH_REFUSED, None, None)
|
||||
|
||||
@fortifyIceFu((False, None))
|
||||
@checkSecret
|
||||
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)
|
||||
|
||||
@fortifyIceFu(-2)
|
||||
@checkSecret
|
||||
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 FROM %sservices_mumbleuser WHERE username = %%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
|
||||
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 username FROM %sservices_mumbleuser WHERE 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
|
||||
|
||||
@fortifyIceFu("")
|
||||
@checkSecret
|
||||
def idToTexture(self, id, current = None):
|
||||
"""
|
||||
Gets called to get the corresponding texture for a user
|
||||
"""
|
||||
|
||||
FALL_THROUGH = ""
|
||||
|
||||
debug('idToTexture "%s" -> fall through', id)
|
||||
return FALL_THROUGH
|
||||
|
||||
@fortifyIceFu(-2)
|
||||
@checkSecret
|
||||
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
|
||||
|
||||
@fortifyIceFu(-1)
|
||||
@checkSecret
|
||||
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
|
||||
|
||||
@fortifyIceFu({})
|
||||
@checkSecret
|
||||
def getRegisteredUsers(self, filter, current = None):
|
||||
"""
|
||||
Returns a list of usernames in the AllianceAuth database which contain
|
||||
filter as a substring.
|
||||
"""
|
||||
|
||||
if not filter:
|
||||
filter = '%'
|
||||
|
||||
try:
|
||||
sql = 'SELECT id, username FROM %sservices_mumbleuser WHERE 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])
|
||||
|
||||
@fortifyIceFu(-1)
|
||||
@checkSecret
|
||||
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
|
||||
|
||||
@fortifyIceFu(-1)
|
||||
@checkSecret
|
||||
def setTexture(self, id, texture, current = None):
|
||||
"""
|
||||
Gets called when the server is asked to update the user texture of a user
|
||||
"""
|
||||
|
||||
FALL_THROUGH = -1
|
||||
|
||||
debug('setTexture %d -> fall through', id)
|
||||
return FALL_THROUGH
|
||||
|
||||
class CustomLogger(Ice.Logger):
|
||||
"""
|
||||
Logger implementation to pipe Ice log messages into
|
||||
our 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 AllianceAuth mumble authenticator')
|
||||
initdata = Ice.InitializationData()
|
||||
initdata.properties = Ice.createProperties([], initdata.properties)
|
||||
for prop, val in cfg.iceraw:
|
||||
initdata.properties.setProperty(prop, val)
|
||||
|
||||
initdata.properties.setProperty('Ice.ImplicitContext', 'Shared')
|
||||
initdata.properties.setProperty('Ice.Default.EncodingVersion', '1.0')
|
||||
initdata.logger = CustomLogger()
|
||||
|
||||
app = allianceauthauthenticatorApp()
|
||||
state = app.main(sys.argv[:1], initData = initdata)
|
||||
info('Shutdown complete')
|
||||
|
||||
|
||||
|
||||
#
|
||||
#--- Python implementation of the AllianceAuth MumbleUser hash function
|
||||
#
|
||||
def allianceauth_check_hash(password, hash):
|
||||
"""
|
||||
Python implementation of the smf check hash function
|
||||
"""
|
||||
return sha1(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)
|
||||
|
||||
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, str(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)
|
Loading…
x
Reference in New Issue
Block a user