@ -34,7 +34,6 @@ class SetAccess(irc.client.SimpleIRCClient):
def __init__ ( self , config , noop , nick , password , server , port ) :
irc . client . SimpleIRCClient . __init__ ( self )
self . identify_msg_cap = False
self . config = config
self . nick = nick
self . password = password
@ -44,6 +43,7 @@ class SetAccess(irc.client.SimpleIRCClient):
self . channels = [ x [ ' name ' ] for x in self . config [ ' channels ' ] ]
self . current_channel = None
self . current_list = [ ]
self . current_mode = ' '
self . changes = [ ]
self . identified = False
if self . port == 6697 :
@ -56,30 +56,19 @@ class SetAccess(irc.client.SimpleIRCClient):
def on_disconnect ( self , connection , event ) :
sys . exit ( 0 )
def on_welcome ( self , c , e ) :
self . identify_msg_cap = False
self . log . debug ( " Requesting identify-msg capability " )
c . cap ( ' REQ ' , ' identify-msg ' )
c . cap ( ' END ' )
def on_cap ( self , c , e ) :
self . log . debug ( " Received cap response %s " % repr ( e . arguments ) )
if e . arguments [ 0 ] == ' ACK ' and ' identify-msg ' in e . arguments [ 1 ] :
self . log . debug ( " identify-msg cap acked " )
self . identify_msg_cap = True
self . log . debug ( " Identifying to nickserv " )
c . privmsg ( " nickserv " , " identify %s " % self . password )
def on_privnotice ( self , c , e ) :
if not self . identify_msg_cap :
self . log . debug ( " Ignoring message because identify-msg "
" cap not enabled " )
return
nick = e . source . split ( ' ! ' ) [ 0 ]
auth = e . arguments [ 0 ] [ 0 ]
msg = e . arguments [ 0 ] [ 1 : ]
if auth == ' + ' and nick == ' NickServ ' and not self . identified :
if msg . startswith ( ' You are now identified ' ) :
msg = e . arguments [ 0 ]
if nick == ' NickServ ' and not self . identified :
if msg . startswith ( ' authenticate yourself to services ' ) :
self . log . debug ( " Identifying to nickserv " )
# TODO (fungi): We should protect against sending our
# password to a false NickServ, perhaps with
# https://www.oftc.net/NickServ/CertFP/ or eventually
# SASL once the ircd implements that
c . privmsg ( " nickserv " , " identify %s " % self . password )
return
elif msg . startswith ( ' You are successfully identified ' ) :
self . identified = True
# Prejoin and set ourselves as op in these channels,
# to facilitate +f forwarding.
@ -88,8 +77,10 @@ class SetAccess(irc.client.SimpleIRCClient):
c . privmsg ( " chanserv " , " op # %s " % channel )
self . advance ( )
return
if auth != ' + ' or nick != ' ChanServ ' :
self . log . debug ( " Ignoring message from unauthenticated "
else :
return
if nick not in ( ' ChanServ ' , ' NickServ ' ) :
self . log . debug ( " Ignoring message from non-ChanServ "
" user %s " % nick )
return
self . failed = False
@ -99,95 +90,78 @@ class SetAccess(irc.client.SimpleIRCClient):
ret = { }
alumni = [ ]
mode = ' '
level = ' '
channel = None
for c in self . config [ ' channels ' ] :
if c [ ' name ' ] == channel_name :
channel = c
if channel is None :
raise Exception ( " Unknown channel %s " % ( channel_name , ) )
mask = ' '
for access , nicks in ( list ( self . config [ ' global ' ] . items ( ) ) +
for key , value in ( list ( self . config [ ' global ' ] . items ( ) ) +
list ( channel . items ( ) ) ) :
if access == ' mask ' :
mask = self . config [ ' access ' ] . get ( nicks )
continue
if access == ' alumni ' :
alumni + = nicks
continue
if access == ' mode ' :
mode = nicks
continue
flags = self . config [ ' access ' ] . get ( access )
if flags is None :
if key == ' alumni ' :
alumni + = value
continue
for nick in nicks :
ret [ nick ] = flags
return mask , ret , alumni , mode
def _get_access_change ( self , current , target , mask ) :
remove = ' '
add = ' '
change = ' '
for x in current :
if x in ' +- ' :
if key == ' mode ' :
mode = value
continue
if target :
if x not in target :
remove + = x
else :
if x not in mask :
remove + = x
for x in target :
if x in ' +- ' :
# If we get this far, we assume the key is an access
# level matching an entry in the access list
level = self . config [ ' access ' ] . get ( key )
if level is None :
# Skip if this doesn't match a defined access level
continue
if x not in current :
add + = x
if remove :
change + = ' - ' + remove
if add :
change + = ' + ' + add
return change
for nick in value :
ret[ nick ] = level
return ret , alumni , mode
def _get_access_change ( self , current , target ) :
if current != target :
return target
def _get_access_changes ( self ) :
mask, target, alumni , mode = self . _get_access_list ( self . current_channel )
self . log . debug ( " Mask for %s : %s " % ( self . current_channel , mask ) )
self . log . debug ( " Target for %s : %s " % ( self . current_channel , target ) )
target, alumni , mode = self . _get_access_list (
self . current_channel )
self . log . debug ( " Target #%s ACL : %s " % ( self . current_channel , target ) )
all_nicks = set ( )
global_alumni = self . config . get ( ' alumni ' , { } )
global_mode = self . config . get ( ' mode ' , ' ' )
current = { }
changes = [ ]
for nick , flags , msg in self . current_list :
for nick , level , msg in self . current_list :
if nick in global_alumni or nick in alumni :
self . log . debug ( " %s is an alumni; removing access " , nick )
changes . append ( ' access # %s del %s ' % ( self . current_channel , nick ) )
continue
all_nicks . add ( nick )
current [ nick ] = flags
current [ nick ] = level
for nick in target . keys ( ) :
all_nicks . add ( nick )
for nick in all_nicks :
change = self . _get_access_change ( current . get ( nick , ' ' ) ,
target . get ( nick , ' ' ) , mask )
target . get ( nick , ' ' ) )
if change :
changes . append ( ' access # %s add %s %s ' % ( self . current_channel ,
nick , change ) )
# Set the mode. Note we always just hard-set the mode for
# simplicity (per the man page mlock always clears and sets
# anyway). Channel mode overrides global mode.
#
# Note for +f you need to be op in the target channel; see
# op_channel option.
# Set the mode if what we want differs from what's already there.
# Channel mode overrides global mode.
if not mode and global_mode :
mode = global_mode
self . log . debug ( " Setting mode to : %s " % mode )
if mode :
if not mode :
mode = ' + '
if sorted ( mode ) != sorted ( self . current_mode ) :
self . log . debug ( " Current mode for # %s is %s , replacing with %s " % (
self . current_channel , self . current_mode , mode ) )
changes . append ( ' set # %s mlock %s ' % ( self . current_channel , mode ) )
return changes
def advance ( self , msg = None ) :
# Some service responses include a number of embedded 0x02 bytes
if msg :
msg = msg . replace ( ' \x02 ' , ' ' )
if self . changes :
if self . noop :
for change in self . changes :
@ -204,19 +178,41 @@ class SetAccess(irc.client.SimpleIRCClient):
self . connection . quit ( )
return
self . current_channel = self . channels . pop ( )
# Clear the mode string before we request it, so if we get
# no response we won't have the modes from an earlier channel
self . current_mode = ' '
# Sending a set mlock with no value prompts the service to
# respond with the current mlock value so we can compare
# against it later
self . connection . privmsg ( ' chanserv ' , ' set # %s mlock ' %
self . current_channel )
# Clear the access list before we request it, so if we get
# no response we won't have the list from an earlier channel
self . current_list = [ ]
self . connection . privmsg ( ' chanserv ' , ' access list # %s ' %
self . connection . privmsg ( ' chanserv ' , ' access #%s list ' %
self . current_channel )
time . sleep ( 1 )
return
if msg . startswith ( ' End of ' ) :
# We tokenize every server message, and perform some rough
# heuristics in order to determine what kind of response we're
# dealing with and whether it's something we know how to parse
parts = msg . split ( )
# If the third word look like an access level, assume this is
# an access list entry and that the second word is a
# corresponding nick
if parts [ 2 ] in ( ' MASTER ' , ' CHANOP ' , ' MEMBER ' ) :
self . current_list . append ( ( parts [ 1 ] , parts [ 2 ] , msg ) )
# If the message starts with "MLOCK is SET to" then assume the
# fifth word is the channel's mode string
elif msg . startswith ( ' MLOCK is SET to ' ) :
self . current_mode = parts [ 4 ]
# If the message starts with "End of" then assume this marks
# the end of an access list
elif msg . startswith ( ' End of ' ) :
self . changes = self . _get_access_changes ( )
self . current_channel = None
self . advance ( )
return
parts = msg . split ( )
if parts [ 2 ] . startswith ( ' + ' ) :
self . current_list . append ( ( parts [ 1 ] , parts [ 2 ] , msg ) )
def main ( ) :