2015-12-07 02:40:13 +01:00
"""
conf . py - PyLink configuration core .
2016-06-21 19:56:53 +02:00
This module is used to access the configuration of the current PyLink instance .
It provides simple checks for validating and loading YAML - format configurations from arbitrary files .
2015-12-07 02:40:13 +01:00
"""
2016-07-20 02:45:43 +02:00
try :
import yaml
except ImportError :
2016-12-10 02:15:53 +01:00
raise ImportError ( " PyLink requires PyYAML to function; please install it and try again. " )
2016-07-20 02:45:43 +02:00
2015-08-04 04:27:19 +02:00
import sys
2016-11-03 06:23:45 +01:00
import os . path
2017-03-16 06:59:48 +01:00
import logging
2015-08-29 04:27:38 +02:00
from collections import defaultdict
2016-06-21 03:18:54 +02:00
from . import world
2015-05-31 21:20:09 +02:00
2017-02-21 19:10:54 +01:00
class ConfigValidationError ( Exception ) :
""" Error when config conditions aren ' t met. """
2016-06-25 23:37:06 +02:00
conf = { ' bot ' :
{
' nick ' : ' PyLink ' ,
' user ' : ' pylink ' ,
' realname ' : ' PyLink Service Client ' ,
' serverdesc ' : ' Unconfigured PyLink '
} ,
' logging ' :
{
' stdout ' : ' INFO '
} ,
' servers ' :
# Wildcard defaultdict! This means that
# any network name you try will work and return
# this basic template:
defaultdict ( lambda : { ' ip ' : ' 0.0.0.0 ' ,
' port ' : 7000 ,
' recvpass ' : " unconfigured " ,
' sendpass ' : " unconfigured " ,
' protocol ' : " null " ,
' hostname ' : " pylink.unconfigured " ,
' sid ' : " 000 " ,
' maxnicklen ' : 20 ,
' sidrange ' : ' 0## '
} )
}
2017-03-11 08:21:17 +01:00
conf [ ' pylink ' ] = conf [ ' bot ' ]
2016-06-25 23:37:06 +02:00
confname = ' unconfigured '
2017-02-21 19:10:54 +01:00
def validate ( condition , errmsg ) :
""" Convenience function to validate conditions in validateConf(). """
if not condition :
raise ConfigValidationError ( errmsg )
2017-03-16 06:59:48 +01:00
def _log ( level , text , * args , logger = None , * * kwargs ) :
if logger :
logger . log ( level , text , * args , * * kwargs )
else :
world . log_queue . append ( ( level , text ) )
2016-12-06 07:43:01 +01:00
def validateConf ( conf , logger = None ) :
2015-09-28 20:30:51 +02:00
""" Validates a parsed configuration dict. """
2017-07-12 23:38:31 +02:00
validate ( isinstance ( conf , dict ) ,
2017-02-21 19:10:54 +01:00
" Invalid configuration given: should be type dict, not %s . "
% type ( conf ) . __name__ )
2016-02-08 03:01:12 +01:00
2017-03-11 08:21:17 +01:00
if ' pylink ' in conf and ' bot ' in conf :
2017-03-16 06:59:48 +01:00
_log ( logging . WARNING , " Since PyLink 1.2, the ' pylink: ' and ' bot: ' configuration sections have been condensed "
2017-06-02 17:34:15 +02:00
" into one. You should merge any options under these sections into one ' pylink: ' block. " , logger = logger )
2017-03-11 08:21:17 +01:00
new_block = conf [ ' bot ' ] . copy ( )
new_block . update ( conf [ ' pylink ' ] )
conf [ ' bot ' ] = conf [ ' pylink ' ] = new_block
elif ' pylink ' in conf :
conf [ ' bot ' ] = conf [ ' pylink ' ]
elif ' bot ' in conf :
conf [ ' pylink ' ] = conf [ ' bot ' ]
# TODO: add a migration warning in the next release.
for section in ( ' pylink ' , ' servers ' , ' login ' , ' logging ' ) :
validate ( conf . get ( section ) , " Missing %r section in config. " % section )
2016-02-08 03:01:12 +01:00
2016-11-08 06:01:28 +01:00
# Make sure at least one form of authentication is valid.
2016-11-23 06:06:54 +01:00
# Also we'll warn them that login:user/login:password is deprecated
if conf [ ' login ' ] . get ( ' password ' ) or conf [ ' login ' ] . get ( ' user ' ) :
2017-03-16 06:59:48 +01:00
_log ( logging . WARNING , " The ' login:user ' and ' login:password ' options are deprecated since PyLink 1.1. "
2017-06-02 17:34:15 +02:00
" Please switch to the new ' login:accounts ' format as outlined in the example config. " , logger = logger )
2016-11-23 06:06:54 +01:00
2017-07-12 23:38:31 +02:00
old_login_valid = isinstance ( conf [ ' login ' ] . get ( ' password ' ) , str ) and isinstance ( conf [ ' login ' ] . get ( ' user ' ) , str )
2016-11-08 06:01:28 +01:00
newlogins = conf [ ' login ' ] . get ( ' accounts ' , { } )
2017-02-25 06:07:28 +01:00
validate ( old_login_valid or newlogins , " No accounts were set, aborting! " )
2016-11-08 06:01:28 +01:00
for account , block in newlogins . items ( ) :
2017-07-12 23:38:31 +02:00
validate ( isinstance ( account , str ) , " Bad username format %s " % account )
validate ( isinstance ( block . get ( ' password ' ) , str ) , " Bad password %s for account %s " % ( block . get ( ' password ' ) , account ) )
2016-11-08 06:01:28 +01:00
2017-02-21 19:10:54 +01:00
validate ( conf [ ' login ' ] . get ( ' password ' ) != " changeme " , " You have not set the login details correctly! " )
2016-02-08 03:01:12 +01:00
2017-02-25 06:07:28 +01:00
if newlogins and not old_login_valid :
2017-02-21 19:10:54 +01:00
validate ( conf . get ( ' permissions ' ) , " New-style accounts enabled but no permissions block was found. You will not be able to administrate your PyLink instance! " )
2017-02-21 03:08:35 +01:00
2017-06-02 17:42:32 +02:00
if conf [ ' logging ' ] . get ( ' stdout ' ) :
_log ( logging . WARNING , ' The log:stdout option is deprecated since PyLink 1.2 in favour of '
' (a more correctly named) log:console. Please update your '
' configuration accordingly! ' , logger = logger )
2015-09-28 20:30:51 +02:00
return conf
2016-11-08 06:01:28 +01:00
2016-12-06 07:43:01 +01:00
def loadConf ( filename , errors_fatal = True , logger = None ) :
2015-09-28 20:30:51 +02:00
""" Loads a PyLink configuration file from the filename given. """
2016-06-21 20:31:39 +02:00
global confname , conf , fname
2016-11-03 06:23:45 +01:00
# Note: store globally the last loaded conf filename, for REHASH in coremods/control.
2016-06-21 20:31:39 +02:00
fname = filename
2016-11-03 06:23:45 +01:00
# For the internal config name, strip off any .yml extensions and absolute paths
confname = os . path . basename ( filename ) . split ( ' . ' , 1 ) [ 0 ]
2016-07-20 02:40:22 +02:00
try :
with open ( filename , ' r ' ) as f :
2015-09-28 20:30:51 +02:00
conf = yaml . load ( f )
2016-12-06 07:43:01 +01:00
conf = validateConf ( conf , logger = logger )
2016-07-20 02:40:22 +02:00
except Exception as e :
2017-07-13 07:40:07 +02:00
e = ' Failed to load config from %r : %s : %s ' % ( filename , type ( e ) . __name__ , e )
2016-07-20 02:40:22 +02:00
2017-07-17 16:55:29 +02:00
if logger : # Prefer using the Python logger when available
2017-07-13 07:40:07 +02:00
logger . exception ( e )
else : # Otherwise, fall back to a print() call.
print ( ' ERROR: %s ' % e , file = sys . stderr )
2017-07-17 16:55:29 +02:00
if errors_fatal :
sys . exit ( 1 )
2016-07-20 02:40:22 +02:00
raise
else :
return conf