2016-06-21 20:25:47 +02:00
"""
corecommands . py - Implements core PyLink commands .
"""
import gc
import sys
2016-07-08 07:48:38 +02:00
import importlib
2016-06-21 20:25:47 +02:00
2016-12-10 06:39:52 +01:00
from . import control , login , permissions
2016-07-28 02:02:04 +02:00
from pylinkirc import utils , world , conf
2016-06-21 20:25:47 +02:00
from pylinkirc . log import log
# Essential, core commands go here so that the "commands" plugin with less-important,
# but still generic functions can be reloaded.
2016-11-08 06:01:28 +01:00
def _login ( irc , source , username ) :
""" Internal function to process logins. """
2018-03-30 20:54:45 +02:00
# Mangle case before we start checking for login data.
accounts = { k . lower ( ) : v for k , v in conf . conf [ ' login ' ] . get ( ' accounts ' , { } ) . items ( ) }
2017-08-08 01:16:17 +02:00
2018-03-30 20:54:45 +02:00
logindata = accounts . get ( username . lower ( ) , { } )
2017-08-08 01:16:17 +02:00
network_filter = logindata . get ( ' networks ' )
2018-03-30 20:30:26 +02:00
require_oper = logindata . get ( ' require_oper ' , False )
hosts_filter = logindata . get ( ' hosts ' , [ ] )
2017-08-08 01:16:17 +02:00
if network_filter and irc . name not in network_filter :
irc . error ( " You are not authorized to log in to %r on this network. " % username )
2018-03-30 20:30:26 +02:00
log . warning ( " ( %s ) Failed login to %r from %s (wrong network: networks filter says %r but we got %r ) " , irc . name , username , irc . getHostmask ( source ) , ' , ' . join ( network_filter ) , irc . name )
return
elif require_oper and not irc . isOper ( source , allowAuthed = False ) :
2018-03-30 20:55:08 +02:00
irc . error ( " You must be opered to log in to %r . " % username )
2018-03-30 20:30:26 +02:00
log . warning ( " ( %s ) Failed login to %r from %s (needs oper) " , irc . name , username , irc . getHostmask ( source ) )
return
elif hosts_filter and not any ( irc . matchHost ( host , source ) for host in hosts_filter ) :
irc . error ( " Failed to log in to %r : hostname mismatch. " % username )
log . warning ( " ( %s ) Failed login to %r from %s (hostname mismatch) " , irc . name , username , irc . getHostmask ( source ) )
2017-08-08 01:16:17 +02:00
return
2016-11-08 06:01:28 +01:00
irc . users [ source ] . account = username
irc . reply ( ' Successfully logged in as %s . ' % username )
log . info ( " ( %s ) Successful login to %r by %s " ,
irc . name , username , irc . getHostmask ( source ) )
def _loginfail ( irc , source , username ) :
""" Internal function to process login failures. """
2016-12-17 04:06:33 +01:00
irc . error ( ' Incorrect credentials. ' )
2016-11-08 06:01:28 +01:00
log . warning ( " ( %s ) Failed login to %r from %s " , irc . name , username , irc . getHostmask ( source ) )
2016-06-21 20:25:47 +02:00
@utils.add_cmd
def identify ( irc , source , args ) :
""" <username> <password>
Logs in to PyLink using the configured administrator account . """
2016-07-14 04:32:39 +02:00
if utils . isChannel ( irc . called_in ) :
2016-06-21 20:25:47 +02:00
irc . reply ( ' Error: This command must be sent in private. '
' (Would you really type a password inside a channel?) ' )
return
try :
username , password = args [ 0 ] , args [ 1 ]
except IndexError :
2016-07-14 04:20:11 +02:00
irc . reply ( ' Error: Not enough arguments. ' )
2016-06-21 20:25:47 +02:00
return
2016-11-08 06:01:28 +01:00
2016-11-20 02:00:18 +01:00
# Process new-style accounts.
if login . checkLogin ( username , password ) :
_login ( irc , source , username )
return
# Process legacy logins (login:user).
if username . lower ( ) == conf . conf [ ' login ' ] . get ( ' user ' , ' ' ) . lower ( ) and password == conf . conf [ ' login ' ] . get ( ' password ' ) :
realuser = conf . conf [ ' login ' ] [ ' user ' ]
_login ( irc , source , realuser )
2016-06-21 20:25:47 +02:00
else :
2016-11-20 02:00:18 +01:00
# Username not found.
_loginfail ( irc , source , username )
2016-11-08 06:01:28 +01:00
2016-06-21 20:25:47 +02:00
@utils.add_cmd
def shutdown ( irc , source , args ) :
""" takes no arguments.
Exits PyLink by disconnecting all networks . """
2016-12-10 06:39:52 +01:00
permissions . checkPermissions ( irc , source , [ ' core.shutdown ' ] )
2016-06-21 20:25:47 +02:00
u = irc . users [ source ]
log . info ( ' ( %s ) SHUTDOWN requested by " %s ! %s @ %s " , exiting... ' , irc . name , u . nick ,
u . ident , u . host )
control . _shutdown ( irc )
@utils.add_cmd
def load ( irc , source , args ) :
""" <plugin name>.
Loads a plugin from the plugin folder . """
2016-12-10 06:39:52 +01:00
# Note: reload capability is acceptable here, because all it actually does is call
# load after unload.
permissions . checkPermissions ( irc , source , [ ' core.load ' , ' core.reload ' ] )
2016-06-21 20:25:47 +02:00
try :
name = args [ 0 ]
except IndexError :
irc . reply ( " Error: Not enough arguments. Needs 1: plugin name. " )
return
if name in world . plugins :
irc . reply ( " Error: %r is already loaded. " % name )
return
log . info ( ' ( %s ) Loading plugin %r for %s ' , irc . name , name , irc . getHostmask ( source ) )
try :
2016-07-24 06:59:25 +02:00
world . plugins [ name ] = pl = utils . loadPlugin ( name )
2016-06-21 20:25:47 +02:00
except ImportError as e :
if str ( e ) == ( ' No module named %r ' % name ) :
log . exception ( ' Failed to load plugin %r : The plugin could not be found. ' , name )
else :
log . exception ( ' Failed to load plugin %r : ImportError. ' , name )
raise
else :
if hasattr ( pl , ' main ' ) :
log . debug ( ' Calling main() function of plugin %r ' , pl )
2017-05-13 04:19:52 +02:00
pl . main ( irc = irc )
2016-06-21 20:25:47 +02:00
irc . reply ( " Loaded plugin %r . " % name )
@utils.add_cmd
def unload ( irc , source , args ) :
""" <plugin name>.
Unloads a currently loaded plugin . """
2016-12-10 06:39:52 +01:00
permissions . checkPermissions ( irc , source , [ ' core.unload ' , ' core.reload ' ] )
2017-02-25 02:49:54 +01:00
2016-06-21 20:25:47 +02:00
try :
name = args [ 0 ]
except IndexError :
irc . reply ( " Error: Not enough arguments. Needs 1: plugin name. " )
return
2016-07-11 06:41:08 +02:00
# Since we're using absolute imports in 0.9.x+, the module name differs from the actual plugin
# name.
2017-03-25 22:08:23 +01:00
modulename = utils . PLUGIN_PREFIX + name
2016-07-11 06:41:08 +02:00
2016-06-21 20:25:47 +02:00
if name in world . plugins :
log . info ( ' ( %s ) Unloading plugin %r for %s ' , irc . name , name , irc . getHostmask ( source ) )
pl = world . plugins [ name ]
log . debug ( ' sys.getrefcount of plugin %s is %s ' , pl , sys . getrefcount ( pl ) )
# Remove any command functions defined by the plugin.
for cmdname , cmdfuncs in world . services [ ' pylink ' ] . commands . copy ( ) . items ( ) :
log . debug ( ' cmdname= %s , cmdfuncs= %s ' , cmdname , cmdfuncs )
for cmdfunc in cmdfuncs :
log . debug ( ' __module__ of cmdfunc %s is %s ' , cmdfunc , cmdfunc . __module__ )
2016-07-11 06:41:08 +02:00
if cmdfunc . __module__ == modulename :
2016-06-21 20:25:47 +02:00
log . debug ( " Removing %s from world.services[ ' pylink ' ].commands[ %s ] " , cmdfunc , cmdname )
world . services [ ' pylink ' ] . commands [ cmdname ] . remove ( cmdfunc )
# If the cmdfunc list is empty, remove it.
if not cmdfuncs :
log . debug ( " Removing world.services[ ' pylink ' ].commands[ %s ] (it ' s empty now) " , cmdname )
del world . services [ ' pylink ' ] . commands [ cmdname ]
# Remove any command hooks set by the plugin.
for hookname , hookfuncs in world . hooks . copy ( ) . items ( ) :
for hookfunc in hookfuncs :
2016-07-11 06:41:08 +02:00
if hookfunc . __module__ == modulename :
2016-06-21 20:25:47 +02:00
world . hooks [ hookname ] . remove ( hookfunc )
# If the hookfuncs list is empty, remove it.
if not hookfuncs :
del world . hooks [ hookname ]
# Call the die() function in the plugin, if present.
if hasattr ( pl , ' die ' ) :
try :
2017-05-13 04:19:52 +02:00
pl . die ( irc = irc )
2016-06-21 20:25:47 +02:00
except : # But don't allow it to crash the server.
log . exception ( ' ( %s ) Error occurred in die() of plugin %s , skipping... ' , irc . name , pl )
# Delete it from memory (hopefully).
del world . plugins [ name ]
2016-07-24 06:59:25 +02:00
for n in ( name , modulename ) :
if n in sys . modules :
del sys . modules [ n ]
if n in globals ( ) :
del globals ( ) [ n ]
2016-06-21 20:25:47 +02:00
# Garbage collect.
gc . collect ( )
irc . reply ( " Unloaded plugin %r . " % name )
return True # We succeeded, make it clear (this status is used by reload() below)
else :
irc . reply ( " Unknown plugin %r . " % name )
@utils.add_cmd
def reload ( irc , source , args ) :
""" <plugin name>.
Loads a plugin from the plugin folder . """
try :
name = args [ 0 ]
except IndexError :
irc . reply ( " Error: Not enough arguments. Needs 1: plugin name. " )
return
2016-12-10 06:39:52 +01:00
# Note: these functions do permission checks, so there are none needed here.
2016-06-21 20:25:47 +02:00
if unload ( irc , source , args ) :
load ( irc , source , args )
@utils.add_cmd
def rehash ( irc , source , args ) :
""" takes no arguments.
Reloads the configuration file for PyLink , ( dis ) connecting added / removed networks .
2016-12-10 06:39:52 +01:00
Note : plugins must be manually reloaded . """
permissions . checkPermissions ( irc , source , [ ' core.rehash ' ] )
2016-06-21 20:25:47 +02:00
try :
control . _rehash ( )
except Exception as e : # Something went wrong, abort.
log . exception ( " Error REHASHing config: " )
irc . reply ( " Error loading configuration file: %s : %s " % ( type ( e ) . __name__ , e ) )
return
else :
irc . reply ( " Done. " )
2017-03-29 07:39:11 +02:00
@utils.add_cmd
def clearqueue ( irc , source , args ) :
""" takes no arguments.
Clears the outgoing text queue for the current connection . """
permissions . checkPermissions ( irc , source , [ ' core.clearqueue ' ] )
2017-04-01 02:41:56 +02:00
irc . queue . queue . clear ( )