2015-07-11 05:26:46 +02:00
# relay.py: PyLink Relay plugin
import sys
import os
sys . path . append ( os . path . dirname ( os . path . dirname ( os . path . abspath ( __file__ ) ) ) )
2015-12-29 20:13:50 +01:00
2015-07-11 05:26:46 +02:00
import pickle
2015-12-29 20:13:50 +01:00
import time
2015-07-11 05:26:46 +02:00
import threading
2015-07-13 01:59:49 +02:00
import string
2015-07-13 08:28:54 +02:00
from collections import defaultdict
2015-07-11 05:26:46 +02:00
2015-08-23 05:51:50 +02:00
from expiringdict import ExpiringDict
2015-07-11 05:26:46 +02:00
import utils
from log import log
2015-08-29 18:39:33 +02:00
import world
2015-07-11 05:26:46 +02:00
2015-12-07 02:13:47 +01:00
### GLOBAL (statekeeping) VARIABLES
2015-07-13 08:28:54 +02:00
relayusers = defaultdict ( dict )
2015-09-12 21:06:58 +02:00
relayservers = defaultdict ( dict )
spawnlocks = defaultdict ( threading . RLock )
2015-09-21 01:56:24 +02:00
spawnlocks_servers = defaultdict ( threading . RLock )
2015-08-23 05:51:50 +02:00
savecache = ExpiringDict ( max_len = 5 , max_age_seconds = 10 )
2015-09-13 02:41:49 +02:00
killcache = ExpiringDict ( max_len = 5 , max_age_seconds = 10 )
2015-12-27 02:06:28 +01:00
2015-12-07 02:13:47 +01:00
dbname = utils . getDatabaseName ( ' pylinkrelay ' )
2015-09-15 02:23:56 +02:00
### INTERNAL FUNCTIONS
def initializeAll ( irc ) :
""" Initializes all relay channels for the given IRC object. """
2015-10-11 00:21:38 +02:00
# Wait for all IRC objects to initialize first. This prevents
# relay servers from being spawned too early (before server authentication),
# which would break connections.
world . started . wait ( 2 )
2015-09-15 02:23:56 +02:00
for chanpair , entrydata in db . items ( ) :
network , channel = chanpair
initializeChannel ( irc , channel )
for link in entrydata [ ' links ' ] :
network , channel = link
initializeChannel ( irc , channel )
2015-09-27 20:56:09 +02:00
def main ( irc = None ) :
2015-09-15 02:23:56 +02:00
""" Main function, called during plugin loading at start. """
2015-12-27 02:06:28 +01:00
2015-09-15 02:23:56 +02:00
loadDB ( )
2015-12-27 02:06:28 +01:00
2015-09-15 02:23:56 +02:00
# Thread this because exportDB() queues itself as part of its
# execution, in order to get a repeating loop.
2015-12-29 20:13:50 +01:00
exportdb_thread = threading . Thread ( target = exportDB , args = ( True , ) , name = " PyLink Relay exportDB Loop " )
exportdb_thread . daemon = True
exportdb_thread . start ( )
2015-12-27 02:06:28 +01:00
2015-09-27 20:56:09 +02:00
if irc is not None :
for ircobj in world . networkobjects . values ( ) :
initializeAll ( ircobj )
def die ( sourceirc ) :
2015-12-27 02:06:28 +01:00
""" Deinitialize PyLink Relay by quitting all relay clients and saving the
relay DB . """
2015-09-27 20:56:09 +02:00
for irc in world . networkobjects . values ( ) :
for user in irc . users . copy ( ) :
if isRelayClient ( irc , user ) :
2016-01-17 01:51:54 +01:00
irc . proto . quit ( user , " Relay plugin unloaded. " )
2015-09-27 20:56:09 +02:00
for server , sobj in irc . servers . copy ( ) . items ( ) :
if hasattr ( sobj , ' remote ' ) :
2016-01-17 01:53:06 +01:00
irc . proto . squit ( irc . sid , server , text = " Relay plugin unloaded. " )
2015-09-27 20:56:09 +02:00
relayservers . clear ( )
relayusers . clear ( )
2015-07-23 09:01:51 +02:00
2015-12-27 02:06:28 +01:00
exportDB ( reschedule = False )
2015-08-23 05:51:50 +02:00
def normalizeNick ( irc , netname , nick , separator = None , uid = ' ' ) :
2015-09-15 02:23:56 +02:00
""" Creates a normalized nickname for the given nick suitable for
introduction to a remote network ( as a relay client ) . """
2015-07-20 23:36:47 +02:00
separator = separator or irc . serverdata . get ( ' separator ' ) or " / "
log . debug ( ' ( %s ) normalizeNick: using %r as separator. ' , irc . name , separator )
2015-07-12 23:02:17 +02:00
orig_nick = nick
2015-09-06 03:00:57 +02:00
protoname = irc . protoname
2015-07-12 23:02:17 +02:00
maxnicklen = irc . maxnicklen
2015-09-01 18:56:28 +02:00
if ' / ' not in separator or not protoname . startswith ( ( ' insp ' , ' unreal ' ) ) :
2015-08-23 05:51:50 +02:00
# Charybdis doesn't allow / in usernames, and will SQUIT with
# a protocol violation if it sees one.
2015-07-12 23:02:17 +02:00
separator = separator . replace ( ' / ' , ' | ' )
nick = nick . replace ( ' / ' , ' | ' )
2015-07-13 01:59:49 +02:00
if nick . startswith ( tuple ( string . digits ) ) :
2015-08-06 06:56:52 +02:00
# On TS6 IRCds, nicks that start with 0-9 are only allowed if
2015-07-13 01:59:49 +02:00
# they match the UID of the originating server. Otherwise, you'll
2015-08-23 05:51:50 +02:00
# get nasty protocol violation SQUITs!
2015-07-13 01:59:49 +02:00
nick = ' _ ' + nick
2015-07-12 23:02:17 +02:00
tagnicks = True
suffix = separator + netname
nick = nick [ : maxnicklen ]
2015-08-23 05:51:50 +02:00
# Maximum allowed length of a nickname, minus the obligatory /network tag.
2015-07-12 23:02:17 +02:00
allowedlength = maxnicklen - len ( suffix )
2015-08-23 05:51:50 +02:00
# If a nick is too long, the real nick portion will be cut off, but the
# /network suffix MUST remain the same.
2015-07-12 23:02:17 +02:00
nick = nick [ : allowedlength ]
nick + = suffix
2015-08-23 05:51:50 +02:00
# The nick we want exists? Darn, create another one then.
# Increase the separator length by 1 if the user was already tagged,
# but couldn't be created due to a nick conflict.
# This can happen when someone steals a relay user's nick.
# However, if the user is changing from, say, a long, cut-off nick to another long,
# cut-off nick, we don't need to check for duplicates and tag the nick twice.
# somecutoffnick/net would otherwise be erroneous NICK'ed to somecutoffnic//net,
# even though there would be no collision because the old and new nicks are from
# the same client.
2016-01-01 02:28:47 +01:00
while irc . nickToUid ( nick ) and irc . nickToUid ( nick ) != uid :
2015-07-12 23:02:17 +02:00
new_sep = separator + separator [ - 1 ]
2015-08-20 12:04:40 +02:00
log . debug ( ' ( %s ) normalizeNick: nick %r is in use; using %r as new_sep. ' , irc . name , nick , new_sep )
2015-07-13 08:28:54 +02:00
nick = normalizeNick ( irc , netname , orig_nick , separator = new_sep )
2015-07-12 23:02:17 +02:00
finalLength = len ( nick )
assert finalLength < = maxnicklen , " Normalized nick %r went over max " \
2015-09-10 05:53:04 +02:00
" nick length (got: %s , allowed: %s !) " % ( nick , finalLength , maxnicklen )
2015-07-12 23:02:17 +02:00
return nick
2015-11-22 08:37:19 +01:00
def normalizeHost ( irc , host ) :
""" Creates a normalized hostname for the given host suitable for
introduction to a remote network ( as a relay client ) . """
if irc . protoname == ' unreal ' :
# UnrealIRCd doesn't allow slashes in hostnames
host = host . replace ( ' / ' , ' . ' )
return host [ : 64 ] # Limited to 64 chars
2015-07-11 05:26:46 +02:00
def loadDB ( ) :
2015-09-15 02:23:56 +02:00
""" Loads the relay database, creating a new one if this fails. """
2015-07-11 05:26:46 +02:00
global db
try :
with open ( dbname , " rb " ) as f :
db = pickle . load ( f )
except ( ValueError , IOError ) :
log . exception ( " Relay: failed to load links database %s "
" , creating a new one in memory... " , dbname )
db = { }
2015-07-19 23:56:12 +02:00
def exportDB ( reschedule = False ) :
2015-09-15 02:23:56 +02:00
""" Exports the relay database, optionally creating a loop to do this
automatically . """
2015-12-29 20:13:50 +01:00
def dump ( ) :
log . debug ( " Relay: exporting links database to %s " , dbname )
with open ( dbname , ' wb ' ) as f :
pickle . dump ( db , f , protocol = 4 )
if reschedule :
while True :
# Sleep for 30 seconds between DB exports. Seems sort of
# arbitrary, but whatever.
time . sleep ( 30 )
dump ( )
else : # Rescheduling was disabled; just dump the DB once.
dump ( )
2015-07-11 05:26:46 +02:00
2015-09-13 22:48:14 +02:00
def getPrefixModes ( irc , remoteirc , channel , user , mlist = None ) :
"""
Fetches all prefix modes for a user in a channel that are supported by the
remote IRC network given .
Optionally , an mlist argument can be given to look at an earlier state of
the channel , e . g . for checking the op status of a mode setter before their
modes are processed and added to the channel state .
"""
2015-07-14 06:46:05 +02:00
modes = ' '
2015-09-13 22:48:14 +02:00
mlist = mlist or irc . channels [ channel ] . prefixmodes
2015-07-14 06:46:05 +02:00
for pmode in ( ' owner ' , ' admin ' , ' op ' , ' halfop ' , ' voice ' ) :
2015-07-23 04:29:58 +02:00
if pmode in remoteirc . cmodes : # Mode supported by IRCd
2015-09-13 22:48:14 +02:00
userlist = mlist [ pmode + ' s ' ]
2015-08-12 10:20:32 +02:00
log . debug ( ' ( %s ) getPrefixModes: checking if %r is in %s list: %r ' ,
2015-09-13 22:48:14 +02:00
irc . name , user , pmode , userlist )
if user in userlist :
2015-07-23 04:29:58 +02:00
modes + = remoteirc . cmodes [ pmode ]
2015-07-14 06:46:05 +02:00
return modes
2015-09-12 21:06:58 +02:00
def getRemoteSid ( irc , remoteirc ) :
2015-09-15 02:23:56 +02:00
""" Gets the remote server SID representing remoteirc on irc, spawning
2015-09-12 21:06:58 +02:00
it if it doesn ' t exist. " " "
2015-10-11 00:21:38 +02:00
# Don't spawn servers too early.
irc . connected . wait ( 2 )
2015-11-16 06:42:58 +01:00
2015-09-29 03:21:52 +02:00
try :
spawnservers = irc . conf [ ' relay ' ] [ ' spawn_servers ' ]
except KeyError :
spawnservers = True
if not spawnservers :
return irc . sid
2015-09-21 01:56:24 +02:00
with spawnlocks_servers [ irc . name ] :
2015-09-12 21:06:58 +02:00
try :
sid = relayservers [ irc . name ] [ remoteirc . name ]
except KeyError :
2015-09-19 07:11:27 +02:00
try :
2015-12-18 06:18:16 +01:00
# ENDBURST is delayed by 3 secs on supported IRCds to prevent
# triggering join-flood protection and the like.
2015-09-20 21:11:41 +02:00
sid = irc . proto . spawnServer ( ' %s .relay ' % remoteirc . name ,
desc = " PyLink Relay network - %s " %
( remoteirc . serverdata . get ( ' netname ' ) \
2015-12-18 06:18:16 +01:00
or remoteirc . name ) , endburst_delay = 3 )
2015-09-19 07:11:27 +02:00
except ValueError : # Network not initialized yet.
log . exception ( ' ( %s ) Failed to spawn server for %r : ' ,
irc . name , remoteirc . name )
2016-01-10 05:03:42 +01:00
irc . disconnect ( )
2015-10-11 00:24:22 +02:00
return
2015-09-27 20:56:09 +02:00
else :
irc . servers [ sid ] . remote = remoteirc . name
2015-09-12 21:06:58 +02:00
relayservers [ irc . name ] [ remoteirc . name ] = sid
return sid
2015-07-20 08:49:50 +02:00
def getRemoteUser ( irc , remoteirc , user , spawnIfMissing = True ) :
2015-09-15 02:23:56 +02:00
""" Gets the UID of the relay client for the given IRC network/user pair,
spawning one if it doesn ' t exist and spawnIfMissing is True. " " "
2015-07-18 01:55:44 +02:00
# If the user (stored here as {('netname', 'UID'):
# {'network1': 'UID1', 'network2': 'UID2'}}) exists, don't spawn it
2015-07-14 21:04:05 +02:00
# again!
2015-07-17 01:27:17 +02:00
try :
if user == irc . pseudoclient . uid :
return remoteirc . pseudoclient . uid
except AttributeError : # Network hasn't been initialized yet?
pass
2015-08-14 17:52:09 +02:00
with spawnlocks [ irc . name ] :
try :
u = relayusers [ ( irc . name , user ) ] [ remoteirc . name ]
except KeyError :
userobj = irc . users . get ( user )
if userobj is None or ( not spawnIfMissing ) or ( not remoteirc . connected . is_set ( ) ) :
# The query wasn't actually a valid user, or the network hasn't
# been connected yet... Oh well!
return
nick = normalizeNick ( remoteirc , irc . name , userobj . nick )
# Truncate idents at 10 characters, because TS6 won't like them otherwise!
ident = userobj . ident [ : 10 ]
2015-11-22 08:37:19 +01:00
# Normalize hostnames
host = normalizeHost ( remoteirc , userobj . host )
2015-08-14 17:52:09 +02:00
realname = userobj . realname
2015-11-22 22:08:31 +01:00
modes = set ( getSupportedUmodes ( irc , remoteirc , userobj . modes ) )
2015-08-31 23:52:56 +02:00
opertype = ' '
2015-08-31 23:23:42 +02:00
if ( ' o ' , None ) in userobj . modes :
if hasattr ( userobj , ' opertype ' ) :
# InspIRCd's special OPERTYPE command; this is mandatory
# and setting of umode +/-o will fail unless this
# is used instead. This also sets an oper type for
# the user, which is used in WHOIS, etc.
# If an opertype exists for the user, add " (remote)"
# for the relayed clone, so that it shows in whois.
# Janus does this too. :)
log . debug ( ' ( %s ) relay.getRemoteUser: setting OPERTYPE of client for %r to %s ' ,
irc . name , user , userobj . opertype )
2015-09-20 20:25:45 +02:00
opertype = userobj . opertype + ' (remote) '
2015-08-31 23:52:56 +02:00
else :
2015-09-20 20:25:45 +02:00
opertype = ' IRC Operator (remote) '
2015-08-31 23:23:42 +02:00
# Set hideoper on remote opers, to prevent inflating
# /lusers and various /stats
hideoper_mode = remoteirc . umodes . get ( ' hideoper ' )
2015-09-19 19:17:25 +02:00
try :
use_hideoper = irc . conf [ ' relay ' ] [ ' hideoper ' ]
except KeyError :
use_hideoper = True
if hideoper_mode and use_hideoper :
2015-11-22 22:08:31 +01:00
modes . add ( ( hideoper_mode , None ) )
2015-09-12 21:06:58 +02:00
rsid = getRemoteSid ( remoteirc , irc )
2015-09-19 19:17:25 +02:00
try :
showRealIP = irc . conf [ ' relay ' ] [ ' show_ips ' ]
except KeyError :
showRealIP = False
if showRealIP :
ip = userobj . ip
realhost = userobj . realhost
else :
realhost = None
ip = ' 0.0.0.0 '
2015-09-29 03:21:52 +02:00
u = remoteirc . proto . spawnClient ( nick , ident = ident ,
2015-09-19 07:11:27 +02:00
host = host , realname = realname ,
modes = modes , ts = userobj . ts ,
2015-09-19 19:17:25 +02:00
opertype = opertype , server = rsid ,
ip = ip , realhost = realhost ) . uid
2015-08-16 04:18:04 +02:00
remoteirc . users [ u ] . remote = ( irc . name , user )
2015-08-31 23:52:56 +02:00
remoteirc . users [ u ] . opertype = opertype
2015-08-14 17:52:09 +02:00
away = userobj . away
if away :
2016-01-17 01:40:36 +01:00
remoteirc . proto . away ( u , away )
2015-08-14 17:52:09 +02:00
relayusers [ ( irc . name , user ) ] [ remoteirc . name ] = u
return u
2015-07-14 21:04:05 +02:00
2015-09-15 02:29:37 +02:00
def getOrigUser ( irc , user , targetirc = None ) :
2015-09-15 02:23:56 +02:00
"""
Given the UID of a relay client , returns a tuple of the home network name
and original UID of the user it was spawned for .
2015-07-22 08:47:06 +02:00
2015-09-15 02:23:56 +02:00
If targetirc is given , getRemoteUser ( ) is called to get the relay client
representing the original user on that target network . """
2015-07-15 04:39:49 +02:00
# First, iterate over everyone!
2015-08-16 04:18:04 +02:00
try :
remoteuser = irc . users [ user ] . remote
except ( AttributeError , KeyError ) :
remoteuser = None
2015-09-15 02:29:37 +02:00
log . debug ( ' ( %s ) getOrigUser: remoteuser set to %r (looking up %s / %s ). ' ,
2015-09-15 02:23:56 +02:00
irc . name , remoteuser , user , irc . name )
2015-07-22 08:47:06 +02:00
if remoteuser :
# If targetirc is given, we'll return simply the UID of the user on the
# target network, if it exists. Otherwise, we'll return a tuple
# with the home network name and the original user's UID.
2015-08-29 18:39:33 +02:00
sourceobj = world . networkobjects . get ( remoteuser [ 0 ] )
2015-07-22 08:47:06 +02:00
if targetirc and sourceobj :
if remoteuser [ 0 ] == targetirc . name :
# The user we found's home network happens to be the one being
# requested; just return the UID then.
return remoteuser [ 1 ]
# Otherwise, use getRemoteUser to find our UID.
2015-09-15 02:23:56 +02:00
res = getRemoteUser ( sourceobj , targetirc , remoteuser [ 1 ] ,
spawnIfMissing = False )
2015-09-15 02:29:37 +02:00
log . debug ( ' ( %s ) getOrigUser: targetirc found, getting %r as '
2015-09-15 02:23:56 +02:00
' remoteuser for %r (looking up %s / %s ). ' , irc . name , res ,
remoteuser [ 1 ] , user , irc . name )
2015-07-22 08:47:06 +02:00
return res
else :
2015-07-15 04:39:49 +02:00
return remoteuser
2015-09-15 02:29:37 +02:00
def getRelay ( chanpair ) :
2015-09-17 05:59:08 +02:00
""" Finds the matching relay entry name for the given (network name, channel)
pair , if one exists . """
2015-07-13 08:28:54 +02:00
if chanpair in db : # This chanpair is a shared channel; others link to it
return chanpair
# This chanpair is linked *to* a remote channel
for name , dbentry in db . items ( ) :
if chanpair in dbentry [ ' links ' ] :
return name
2015-07-13 04:03:18 +02:00
2015-09-15 02:29:37 +02:00
def getRemoteChan ( irc , remoteirc , channel ) :
2015-09-15 02:23:56 +02:00
""" Returns the linked channel name for the given channel on remoteirc,
if one exists . """
2015-07-14 21:04:05 +02:00
query = ( irc . name , channel )
remotenetname = remoteirc . name
2015-09-15 02:29:37 +02:00
chanpair = getRelay ( query )
2015-07-14 04:46:24 +02:00
if chanpair is None :
return
if chanpair [ 0 ] == remotenetname :
return chanpair [ 1 ]
else :
for link in db [ chanpair ] [ ' links ' ] :
if link [ 0 ] == remotenetname :
return link [ 1 ]
2015-07-13 09:01:04 +02:00
def initializeChannel ( irc , channel ) :
2015-09-15 02:23:56 +02:00
""" Initializes a relay channel (merge local/remote users, set modes, etc.). """
2015-07-14 06:46:05 +02:00
# We're initializing a relay that already exists. This can be done at
# ENDBURST, or on the LINK command.
2015-09-15 02:29:37 +02:00
relay = getRelay ( ( irc . name , channel ) )
2015-07-14 06:46:05 +02:00
log . debug ( ' ( %s ) initializeChannel being called on %s ' , irc . name , channel )
log . debug ( ' ( %s ) initializeChannel: relay pair found to be %s ' , irc . name , relay )
2015-07-14 21:04:05 +02:00
queued_users = [ ]
2015-07-14 06:46:05 +02:00
if relay :
all_links = db [ relay ] [ ' links ' ] . copy ( )
all_links . update ( ( relay , ) )
log . debug ( ' ( %s ) initializeChannel: all_links: %s ' , irc . name , all_links )
2015-07-23 04:29:58 +02:00
# Iterate over all the remote channels linked in this relay.
2015-07-14 06:46:05 +02:00
for link in all_links :
2015-07-13 09:01:04 +02:00
remotenet , remotechan = link
2015-07-14 06:46:05 +02:00
if remotenet == irc . name :
continue
2015-08-29 18:39:33 +02:00
remoteirc = world . networkobjects . get ( remotenet )
2015-07-21 08:36:26 +02:00
if remoteirc is None :
continue
2015-07-14 06:46:05 +02:00
rc = remoteirc . channels [ remotechan ]
2015-09-15 02:29:37 +02:00
if not ( remoteirc . connected . is_set ( ) and getRemoteChan ( remoteirc , irc , remotechan ) ) :
2015-07-16 21:25:09 +02:00
continue # They aren't connected, don't bother!
2015-07-23 04:29:58 +02:00
# Join their (remote) users and set their modes.
2015-08-16 08:31:54 +02:00
relayJoins ( remoteirc , remotechan , rc . users , rc . ts )
2015-08-16 08:05:09 +02:00
topic = remoteirc . channels [ remotechan ] . topic
2015-07-20 22:18:04 +02:00
# Only update the topic if it's different from what we already have,
# and topic bursting is complete.
2015-08-16 08:05:09 +02:00
if remoteirc . channels [ remotechan ] . topicset and topic != irc . channels [ channel ] . topic :
2016-01-17 02:09:52 +01:00
irc . proto . topicBurst ( getRemoteSid ( irc , remoteirc ) , channel , topic )
2015-09-03 02:41:49 +02:00
# Send our users and channel modes to the other nets
2015-09-12 21:06:58 +02:00
log . debug ( ' ( %s ) initializeChannel: joining our ( %s ) users: %s ' , irc . name , remotenet , irc . channels [ channel ] . users )
relayJoins ( irc , channel , irc . channels [ channel ] . users , irc . channels [ channel ] . ts )
2015-10-25 18:39:56 +01:00
if irc . pseudoclient . uid not in irc . channels [ channel ] . users :
2016-01-17 01:36:45 +01:00
irc . proto . join ( irc . pseudoclient . uid , channel )
2015-07-14 21:04:05 +02:00
2015-09-15 02:23:56 +02:00
def removeChannel ( irc , channel ) :
""" Destroys a relay channel by parting all of its users. """
if irc is None :
2015-07-29 13:02:45 +02:00
return
2015-09-15 02:23:56 +02:00
if channel not in map ( str . lower , irc . serverdata [ ' channels ' ] ) :
2016-01-17 01:51:54 +01:00
irc . proto . part ( irc . pseudoclient . uid , channel , ' Channel delinked. ' )
2015-09-15 02:29:37 +02:00
relay = getRelay ( ( irc . name , channel ) )
2015-09-15 02:23:56 +02:00
if relay :
for user in irc . channels [ channel ] . users . copy ( ) :
if not isRelayClient ( irc , user ) :
relayPart ( irc , channel , user )
# Don't ever part the main client from any of its autojoin channels.
2015-08-15 09:02:46 +02:00
else :
2015-09-15 02:23:56 +02:00
if user == irc . pseudoclient . uid and channel in \
irc . serverdata [ ' channels ' ] :
continue
2016-01-17 01:51:54 +01:00
irc . proto . part ( user , channel , ' Channel delinked. ' )
2015-09-15 02:23:56 +02:00
# Don't ever quit it either...
if user != irc . pseudoclient . uid and not irc . users [ user ] . channels :
2015-09-15 02:29:37 +02:00
remoteuser = getOrigUser ( irc , user )
2015-09-15 02:23:56 +02:00
del relayusers [ remoteuser ] [ irc . name ]
2016-01-17 01:51:54 +01:00
irc . proto . quit ( user , ' Left all shared channels. ' )
2015-07-15 04:39:49 +02:00
2015-09-13 22:48:14 +02:00
def checkClaim ( irc , channel , sender , chanobj = None ) :
2015-09-13 07:50:53 +02:00
"""
Checks whether the sender of a kick / mode change passes CLAIM checks for
a given channel . This returns True if any of the following criteria are met :
2015-09-17 05:59:08 +02:00
1 ) No relay exists for the channel in question .
2 ) The originating network is the one that created the relay .
3 ) The CLAIM list for the relay in question is empty .
4 ) The originating network is in the CLAIM list for the relay in question .
5 ) The sender is halfop or above in the channel .
6 ) The sender is a PyLink client / server ( checks are suppressed in this case ) .
2015-09-13 07:50:53 +02:00
"""
2015-09-15 02:29:37 +02:00
relay = getRelay ( ( irc . name , channel ) )
2015-09-13 22:48:14 +02:00
try :
mlist = chanobj . prefixmodes
except AttributeError :
mlist = None
sender_modes = getPrefixModes ( irc , irc , channel , sender , mlist = mlist )
log . debug ( ' ( %s ) relay.checkClaim: sender modes ( %s / %s ) are %s (mlist= %s ) ' , irc . name ,
sender , channel , sender_modes , mlist )
2015-09-17 05:59:08 +02:00
return ( not relay ) or irc . name == relay [ 0 ] or not db [ relay ] [ ' claim ' ] or \
irc . name in db [ relay ] [ ' claim ' ] or \
2015-09-13 07:50:53 +02:00
any ( [ mode in sender_modes for mode in ( ' y ' , ' q ' , ' a ' , ' o ' , ' h ' ) ] ) \
2016-01-01 02:28:47 +01:00
or irc . isInternalClient ( sender ) or \
irc . isInternalServer ( sender )
2015-09-13 07:50:53 +02:00
2015-09-15 02:23:56 +02:00
def getSupportedUmodes ( irc , remoteirc , modes ) :
""" Given a list of user modes, filters out all of those not supported by the
remote network . """
supported_modes = [ ]
for modepair in modes :
try :
prefix , modechar = modepair [ 0 ]
except ValueError :
modechar = modepair [ 0 ]
prefix = ' + '
arg = modepair [ 1 ]
for name , m in irc . umodes . items ( ) :
supported_char = None
if modechar == m :
if name not in whitelisted_umodes :
log . debug ( " ( %s ) getSupportedUmodes: skipping mode ( %r , %r ) because "
" it isn ' t a whitelisted (safe) mode for relay. " ,
irc . name , modechar , arg )
break
supported_char = remoteirc . umodes . get ( name )
if supported_char :
supported_modes . append ( ( prefix + supported_char , arg ) )
break
else :
log . debug ( " ( %s ) getSupportedUmodes: skipping mode ( %r , %r ) because "
" the remote network ( %s ) ' s IRCd ( %s ) doesn ' t support it. " ,
irc . name , modechar , arg , remoteirc . name ,
remoteirc . protoname )
return supported_modes
def isRelayClient ( irc , user ) :
""" Returns whether the given user is a relay client. """
try :
if irc . users [ user ] . remote :
# Is the .remote attribute set? If so, don't relay already
# relayed clients; that'll trigger an endless loop!
return True
except AttributeError : # Nope, it isn't.
pass
except KeyError : # The user doesn't exist?!?
return True
return False
### EVENT HANDLER INTERNALS
def relayJoins ( irc , channel , users , ts , burst = True ) :
2015-11-27 07:50:20 +01:00
for name , remoteirc in world . networkobjects . copy ( ) . items ( ) :
2015-09-15 02:23:56 +02:00
queued_users = [ ]
if name == irc . name or not remoteirc . connected . is_set ( ) :
# Don't relay things to their source network...
2015-07-15 03:20:20 +02:00
continue
2015-09-15 02:29:37 +02:00
remotechan = getRemoteChan ( irc , remoteirc , channel )
2015-07-15 03:20:20 +02:00
if remotechan is None :
2015-09-15 02:23:56 +02:00
# If there is no link on our network for the user, don't
# bother spawning it.
2015-07-15 03:20:20 +02:00
continue
2015-09-15 02:23:56 +02:00
log . debug ( ' ( %s ) relayJoins: got %r for users ' , irc . name , users )
for user in users . copy ( ) :
if isRelayClient ( irc , user ) :
# Don't clone relay clients; that'll cause some bad, bad
# things to happen.
continue
log . debug ( ' Okay, spawning %s / %s everywhere ' , user , irc . name )
assert user in irc . users , " ( %s ) How is this possible? %r isn ' t in our user database. " % ( irc . name , user )
u = getRemoteUser ( irc , remoteirc , user )
# Only join users if they aren't already joined. This prevents op floods
# on charybdis from all the SJOINing.
if u not in remoteirc . channels [ remotechan ] . users :
ts = irc . channels [ channel ] . ts
prefixes = getPrefixModes ( irc , remoteirc , channel , user )
userpair = ( prefixes , u )
queued_users . append ( userpair )
log . debug ( ' ( %s ) relayJoins: joining %s to %s %s ' , irc . name , userpair , remoteirc . name , remotechan )
else :
log . debug ( ' ( %s ) relayJoins: not joining %s to %s %s ; they \' re already there! ' , irc . name ,
u , remoteirc . name , remotechan )
if queued_users :
# Burst was explicitly given, or we're trying to join multiple
# users/someone with a prefix.
if burst or len ( queued_users ) > 1 or queued_users [ 0 ] [ 0 ] :
rsid = getRemoteSid ( remoteirc , irc )
2016-01-17 01:53:46 +01:00
remoteirc . proto . sjoin ( rsid , remotechan , queued_users , ts = ts )
2015-09-15 02:23:56 +02:00
relayModes ( irc , remoteirc , getRemoteSid ( irc , remoteirc ) , channel , irc . channels [ channel ] . modes )
else :
2016-01-17 01:36:45 +01:00
remoteirc . proto . join ( queued_users [ 0 ] [ 1 ] , remotechan )
2015-07-20 08:49:50 +02:00
2015-09-15 02:23:56 +02:00
def relayPart ( irc , channel , user ) :
for name , remoteirc in world . networkobjects . items ( ) :
if name == irc . name or not remoteirc . connected . is_set ( ) :
# Don't relay things to their source network...
2015-08-18 11:44:27 +02:00
continue
2015-09-15 02:29:37 +02:00
remotechan = getRemoteChan ( irc , remoteirc , channel )
2015-09-15 02:23:56 +02:00
log . debug ( ' ( %s ) relayPart: looking for %s / %s on %s ' , irc . name , user , irc . name , remoteirc . name )
log . debug ( ' ( %s ) relayPart: remotechan found as %s ' , irc . name , remotechan )
remoteuser = getRemoteUser ( irc , remoteirc , user , spawnIfMissing = False )
log . debug ( ' ( %s ) relayPart: remoteuser for %s / %s found as %s ' , irc . name , user , irc . name , remoteuser )
if remotechan is None or remoteuser is None :
continue
2016-01-17 01:51:54 +01:00
remoteirc . proto . part ( remoteuser , remotechan , ' Channel delinked. ' )
2015-09-15 02:23:56 +02:00
if isRelayClient ( remoteirc , remoteuser ) and not remoteirc . users [ remoteuser ] . channels :
2016-01-17 01:51:54 +01:00
remoteirc . proto . quit ( remoteuser , ' Left all shared channels. ' )
2015-09-15 02:23:56 +02:00
del relayusers [ ( irc . name , user ) ] [ remoteirc . name ]
2015-07-16 20:53:40 +02:00
2015-07-20 00:56:29 +02:00
whitelisted_cmodes = { ' admin ' , ' allowinvite ' , ' autoop ' , ' ban ' , ' banexception ' ,
' blockcolor ' , ' halfop ' , ' invex ' , ' inviteonly ' , ' key ' ,
' limit ' , ' moderated ' , ' noctcp ' , ' noextmsg ' , ' nokick ' ,
' noknock ' , ' nonick ' , ' nonotice ' , ' op ' , ' operonly ' ,
' opmoderated ' , ' owner ' , ' private ' , ' regonly ' ,
2015-07-25 05:55:48 +02:00
' regmoderated ' , ' secret ' , ' sslonly ' , ' adminonly ' ,
2015-07-20 00:56:29 +02:00
' stripcolor ' , ' topiclock ' , ' voice ' }
2015-07-20 07:43:26 +02:00
whitelisted_umodes = { ' bot ' , ' hidechans ' , ' hideoper ' , ' invisible ' , ' oper ' ,
2015-11-22 08:57:24 +01:00
' regdeaf ' , ' u_stripcolor ' , ' u_noctcp ' , ' wallops ' ,
2015-11-22 22:08:31 +01:00
' hideidle ' }
2015-07-15 04:39:49 +02:00
def relayModes ( irc , remoteirc , sender , channel , modes = None ) :
2015-09-15 02:29:37 +02:00
remotechan = getRemoteChan ( irc , remoteirc , channel )
2015-07-15 04:39:49 +02:00
log . debug ( ' ( %s ) Relay mode: remotechan for %s on %s is %s ' , irc . name , channel , irc . name , remotechan )
if remotechan is None :
return
if modes is None :
2015-08-16 04:53:09 +02:00
modes = irc . channels [ channel ] . modes
log . debug ( ' ( %s ) Relay mode: channel data for %s %s : %s ' , irc . name , remoteirc . name , remotechan , remoteirc . channels [ remotechan ] )
2015-07-15 04:39:49 +02:00
supported_modes = [ ]
log . debug ( ' ( %s ) Relay mode: initial modelist for %s is %s ' , irc . name , channel , modes )
for modepair in modes :
try :
prefix , modechar = modepair [ 0 ]
except ValueError :
modechar = modepair [ 0 ]
prefix = ' + '
arg = modepair [ 1 ]
# Iterate over every mode see whether the remote IRCd supports
# this mode, and what its mode char for it is (if it is different).
for name , m in irc . cmodes . items ( ) :
supported_char = None
if modechar == m :
2015-07-22 08:53:29 +02:00
supported_char = remoteirc . cmodes . get ( name )
if supported_char is None :
break
2015-07-20 00:56:29 +02:00
if name not in whitelisted_cmodes :
log . debug ( " ( %s ) Relay mode: skipping mode ( %r , %r ) because "
" it isn ' t a whitelisted (safe) mode for relay. " ,
irc . name , modechar , arg )
break
2015-07-15 04:39:49 +02:00
if modechar in irc . prefixmodes :
# This is a prefix mode (e.g. +o). We must coerse the argument
# so that the target exists on the remote relay network.
2015-07-22 07:14:53 +02:00
log . debug ( " ( %s ) Relay mode: coersing argument of ( %r , %r ) "
" for network %r . " ,
irc . name , modechar , arg , remoteirc . name )
2015-07-22 08:47:06 +02:00
# If the target is a remote user, get the real target
# (original user).
2015-09-15 02:29:37 +02:00
arg = getOrigUser ( irc , arg , targetirc = remoteirc ) or \
2015-07-22 08:47:06 +02:00
getRemoteUser ( irc , remoteirc , arg , spawnIfMissing = False )
log . debug ( " ( %s ) Relay mode: argument found as ( %r , %r ) "
" for network %r . " ,
irc . name , modechar , arg , remoteirc . name )
2015-07-25 19:43:47 +02:00
oplist = remoteirc . channels [ remotechan ] . prefixmodes [ name + ' s ' ]
log . debug ( " ( %s ) Relay mode: list of %s s on %r is: %s " ,
irc . name , name , remotechan , oplist )
if prefix == ' + ' and arg in oplist :
2015-07-22 08:53:29 +02:00
# Don't set prefix modes that are already set.
log . debug ( " ( %s ) Relay mode: skipping setting %s on %s / %s because it appears to be already set. " ,
irc . name , name , arg , remoteirc . name )
break
2015-07-15 04:39:49 +02:00
supported_char = remoteirc . cmodes . get ( name )
if supported_char :
2015-07-25 19:43:47 +02:00
final_modepair = ( prefix + supported_char , arg )
2015-07-20 00:20:23 +02:00
if name in ( ' ban ' , ' banexception ' , ' invex ' ) and not utils . isHostmask ( arg ) :
# Don't add bans that don't match n!u@h syntax!
log . debug ( " ( %s ) Relay mode: skipping mode ( %r , %r ) because it doesn ' t match nick!user@host syntax. " ,
2015-07-20 00:56:29 +02:00
irc . name , modechar , arg )
break
2015-07-22 08:53:29 +02:00
# Don't set modes that are already set, to prevent floods on TS6
# where the same mode can be set infinite times.
2015-07-25 19:43:47 +02:00
if prefix == ' + ' and final_modepair in remoteirc . channels [ remotechan ] . modes :
2015-07-22 08:53:29 +02:00
log . debug ( " ( %s ) Relay mode: skipping setting mode ( %r , %r ) on %s %s because it appears to be already set. " ,
irc . name , supported_char , arg , remoteirc . name , remotechan )
break
2015-07-25 19:43:47 +02:00
supported_modes . append ( final_modepair )
2015-07-15 04:39:49 +02:00
log . debug ( ' ( %s ) Relay mode: final modelist (sending to %s %s ) is %s ' , irc . name , remoteirc . name , remotechan , supported_modes )
2015-07-17 01:27:17 +02:00
# Don't send anything if there are no supported modes left after filtering.
if supported_modes :
# Check if the sender is a user; remember servers are allowed to set modes too.
2015-08-21 07:13:28 +02:00
u = getRemoteUser ( irc , remoteirc , sender , spawnIfMissing = False )
if u :
2016-01-17 02:08:17 +01:00
remoteirc . proto . mode ( u , remotechan , supported_modes )
2015-07-22 07:14:53 +02:00
else :
2015-09-12 21:06:58 +02:00
rsid = getRemoteSid ( remoteirc , irc )
2016-01-17 02:08:17 +01:00
remoteirc . proto . mode ( rsid , remotechan , supported_modes )
2015-07-15 04:39:49 +02:00
2015-09-15 02:23:56 +02:00
def relayWhoisHandler ( irc , target ) :
user = irc . users [ target ]
2015-09-15 02:29:37 +02:00
orig = getOrigUser ( irc , target )
2015-09-15 02:23:56 +02:00
if orig :
network , remoteuid = orig
remotenick = world . networkobjects [ network ] . users [ remoteuid ] . nick
return [ 320 , " %s :is a remote user connected via PyLink Relay. Home "
" network: %s ; Home nick: %s " % ( user . nick , network ,
remotenick ) ]
world . whois_handlers . append ( relayWhoisHandler )
### GENERIC EVENT HOOK HANDLERS
def handle_operup ( irc , numeric , command , args ) :
newtype = args [ ' text ' ] + ' _(remote) '
for netname , user in relayusers [ ( irc . name , numeric ) ] . items ( ) :
2015-09-20 20:25:45 +02:00
log . debug ( ' ( %s ) relay.handle_opertype: setting OPERTYPE of %s / %s to %s ' ,
irc . name , user , netname , newtype )
2015-09-15 02:23:56 +02:00
remoteirc = world . networkobjects [ netname ]
remoteirc . users [ user ] . opertype = newtype
2015-12-27 00:41:22 +01:00
utils . add_hook ( handle_operup , ' CLIENT_OPERED ' )
2015-09-15 02:23:56 +02:00
def handle_join ( irc , numeric , command , args ) :
channel = args [ ' channel ' ]
2015-09-15 02:29:37 +02:00
if not getRelay ( ( irc . name , channel ) ) :
2015-09-15 02:23:56 +02:00
# No relay here, return.
return
ts = args [ ' ts ' ]
users = set ( args [ ' users ' ] )
relayJoins ( irc , channel , users , ts , burst = False )
utils . add_hook ( handle_join , ' JOIN ' )
def handle_quit ( irc , numeric , command , args ) :
for netname , user in relayusers [ ( irc . name , numeric ) ] . copy ( ) . items ( ) :
remoteirc = world . networkobjects [ netname ]
2016-01-17 01:51:54 +01:00
remoteirc . proto . quit ( user , args [ ' text ' ] )
2015-09-15 02:23:56 +02:00
del relayusers [ ( irc . name , numeric ) ]
utils . add_hook ( handle_quit , ' QUIT ' )
def handle_squit ( irc , numeric , command , args ) :
users = args [ ' users ' ]
target = args [ ' target ' ]
# Someone /SQUIT one of our relay subservers. Bad! Rejoin them!
if target in relayservers [ irc . name ] . values ( ) :
sname = args [ ' name ' ]
remotenet = sname . split ( ' . ' , 1 ) [ 0 ]
del relayservers [ irc . name ] [ remotenet ]
for userpair in relayusers :
if userpair [ 0 ] == remotenet and irc . name in relayusers [ userpair ] :
del relayusers [ userpair ] [ irc . name ]
remoteirc = world . networkobjects [ remotenet ]
initializeAll ( remoteirc )
else :
# Some other netsplit happened on the network, we'll have to fake
# some *.net *.split quits for that.
for user in users :
log . debug ( ' ( %s ) relay handle_squit: sending handle_quit on %s ' , irc . name , user )
handle_quit ( irc , user , command , { ' text ' : ' *.net *.split ' } )
utils . add_hook ( handle_squit , ' SQUIT ' )
def handle_nick ( irc , numeric , command , args ) :
for netname , user in relayusers [ ( irc . name , numeric ) ] . items ( ) :
remoteirc = world . networkobjects [ netname ]
newnick = normalizeNick ( remoteirc , irc . name , args [ ' newnick ' ] , uid = user )
if remoteirc . users [ user ] . nick != newnick :
2016-01-17 01:51:04 +01:00
remoteirc . proto . nick ( user , newnick )
2015-09-15 02:23:56 +02:00
utils . add_hook ( handle_nick , ' NICK ' )
def handle_part ( irc , numeric , command , args ) :
channels = args [ ' channels ' ]
text = args [ ' text ' ]
# Don't allow the PyLink client PARTing to be relayed.
if numeric == irc . pseudoclient . uid :
return
for channel in channels :
for netname , user in relayusers [ ( irc . name , numeric ) ] . copy ( ) . items ( ) :
remoteirc = world . networkobjects [ netname ]
2015-09-15 02:29:37 +02:00
remotechan = getRemoteChan ( irc , remoteirc , channel )
2015-09-15 02:23:56 +02:00
if remotechan is None :
continue
2016-01-17 01:51:54 +01:00
remoteirc . proto . part ( user , remotechan , text )
2015-09-15 02:23:56 +02:00
if not remoteirc . users [ user ] . channels :
2016-01-17 01:51:54 +01:00
remoteirc . proto . quit ( user , ' Left all shared channels. ' )
2015-09-15 02:23:56 +02:00
del relayusers [ ( irc . name , numeric ) ] [ remoteirc . name ]
utils . add_hook ( handle_part , ' PART ' )
2015-09-26 19:10:54 +02:00
def handle_messages ( irc , numeric , command , args ) :
notice = ( command in ( ' NOTICE ' , ' PYLINK_SELF_NOTICE ' ) )
2015-09-15 02:23:56 +02:00
target = args [ ' target ' ]
text = args [ ' text ' ]
2016-01-01 02:28:47 +01:00
if irc . isInternalClient ( numeric ) and irc . isInternalClient ( target ) :
2015-11-27 07:51:19 +01:00
# Drop attempted PMs between internal clients (this shouldn't happen,
# but whatever).
return
elif numeric in irc . servers :
# Sender is a server? This shouldn't be allowed, except for some truly
# special cases... We'll route these through the main PyLink client,
# tagging the message with the sender name.
text = ' [from %s ] %s ' % ( irc . servers [ numeric ] . name , text )
numeric = irc . pseudoclient . uid
elif numeric not in irc . users :
# Sender didn't pass the check above, AND isn't a user.
2015-12-07 02:40:06 +01:00
log . debug ( ' ( %s ) relay: Unknown message sender %s . ' , irc . name , numeric )
2015-09-15 02:23:56 +02:00
return
2015-09-15 02:29:37 +02:00
relay = getRelay ( ( irc . name , target ) )
2015-09-15 02:23:56 +02:00
remoteusers = relayusers [ ( irc . name , numeric ) ]
# HACK: Don't break on sending to @#channel or similar.
try :
prefix , target = target . split ( ' # ' , 1 )
except ValueError :
prefix = ' '
else :
target = ' # ' + target
log . debug ( ' ( %s ) relay privmsg: prefix is %r , target is %r ' , irc . name , prefix , target )
if utils . isChannel ( target ) and relay and numeric not in irc . channels [ target ] . users :
# The sender must be in the target channel to send messages over the relay;
# it's the only way we can make sure they have a spawned client on ALL
# of the linked networks. This affects -n channels too; see
# https://github.com/GLolol/PyLink/issues/91 for an explanation of why.
irc . msg ( numeric , ' Error: You must be in %r in order to send '
' messages over the relay. ' % target , notice = True )
return
if utils . isChannel ( target ) :
2015-09-26 19:10:54 +02:00
for name , remoteirc in world . networkobjects . items ( ) :
2015-09-15 02:29:37 +02:00
real_target = getRemoteChan ( irc , remoteirc , target )
2015-09-26 19:10:54 +02:00
if irc . name == name or not remoteirc . connected . is_set ( ) or not real_target :
2015-09-15 02:23:56 +02:00
continue
2015-09-26 19:10:54 +02:00
user = getRemoteUser ( irc , remoteirc , numeric , spawnIfMissing = False )
2015-09-15 02:23:56 +02:00
real_target = prefix + real_target
if notice :
2016-01-17 01:44:23 +01:00
remoteirc . proto . notice ( user , real_target , text )
2015-09-15 02:23:56 +02:00
else :
2016-01-17 01:44:23 +01:00
remoteirc . proto . message ( user , real_target , text )
2015-09-15 02:23:56 +02:00
else :
2015-09-15 02:29:37 +02:00
remoteuser = getOrigUser ( irc , target )
2015-09-15 02:23:56 +02:00
if remoteuser is None :
return
homenet , real_target = remoteuser
# For PMs, we must be on a common channel with the target.
# Otherwise, the sender doesn't have a client representing them
# on the remote network, and we won't have anything to send our
# messages from.
if homenet not in remoteusers . keys ( ) :
irc . msg ( numeric , ' Error: You must be in a common channel '
' with %r in order to send messages. ' % \
irc . users [ target ] . nick , notice = True )
return
remoteirc = world . networkobjects [ homenet ]
user = getRemoteUser ( irc , remoteirc , numeric , spawnIfMissing = False )
if notice :
2016-01-17 01:44:23 +01:00
remoteirc . proto . notice ( user , real_target , text )
2015-07-20 07:43:26 +02:00
else :
2016-01-17 01:44:23 +01:00
remoteirc . proto . message ( user , real_target , text )
2015-09-26 19:10:54 +02:00
for cmd in ( ' PRIVMSG ' , ' NOTICE ' , ' PYLINK_SELF_NOTICE ' , ' PYLINK_SELF_PRIVMSG ' ) :
utils . add_hook ( handle_messages , cmd )
2015-09-15 02:23:56 +02:00
def handle_kick ( irc , source , command , args ) :
channel = args [ ' channel ' ]
target = args [ ' target ' ]
text = args [ ' text ' ]
kicker = source
2015-09-15 02:29:37 +02:00
relay = getRelay ( ( irc . name , channel ) )
2015-09-15 02:23:56 +02:00
# Don't allow kicks to the PyLink client to be relayed.
if relay is None or target == irc . pseudoclient . uid :
return
2015-09-15 02:29:37 +02:00
origuser = getOrigUser ( irc , target )
2015-09-15 02:23:56 +02:00
for name , remoteirc in world . networkobjects . items ( ) :
if irc . name == name or not remoteirc . connected . is_set ( ) :
continue
2015-09-15 02:29:37 +02:00
remotechan = getRemoteChan ( irc , remoteirc , channel )
2015-09-15 02:23:56 +02:00
log . debug ( ' ( %s ) Relay kick: remotechan for %s on %s is %s ' , irc . name , channel , name , remotechan )
if remotechan is None :
continue
real_kicker = getRemoteUser ( irc , remoteirc , kicker , spawnIfMissing = False )
log . debug ( ' ( %s ) Relay kick: real kicker for %s on %s is %s ' , irc . name , kicker , name , real_kicker )
if not isRelayClient ( irc , target ) :
log . debug ( ' ( %s ) Relay kick: target %s is NOT an internal client ' , irc . name , target )
# Both the target and kicker are external clients; i.e.
# they originate from the same network. We won't have
# to filter this; the uplink IRCd will handle it appropriately,
# and we'll just follow.
real_target = getRemoteUser ( irc , remoteirc , target , spawnIfMissing = False )
log . debug ( ' ( %s ) Relay kick: real target for %s is %s ' , irc . name , target , real_target )
else :
log . debug ( ' ( %s ) Relay kick: target %s is an internal client, going to look up the real user ' , irc . name , target )
2015-09-15 02:29:37 +02:00
real_target = getOrigUser ( irc , target , targetirc = remoteirc )
2015-09-15 02:23:56 +02:00
if not checkClaim ( irc , channel , kicker ) :
log . debug ( ' ( %s ) Relay kick: kicker %s is not opped... We should rejoin the target user %s ' , irc . name , kicker , real_target )
# Home network is not in the channel's claim AND the kicker is not
# opped. We won't propograte the kick then.
# TODO: make the check slightly more advanced: i.e. halfops can't
# kick ops, admins can't kick owners, etc.
modes = getPrefixModes ( remoteirc , irc , remotechan , real_target )
# Join the kicked client back with its respective modes.
2016-01-17 01:53:46 +01:00
irc . proto . sjoin ( irc . sid , channel , [ ( modes , target ) ] )
2015-09-15 02:23:56 +02:00
if kicker in irc . users :
2015-09-19 19:32:45 +02:00
log . info ( ' ( %s ) Relay claim: Blocked KICK (reason %r ) from %s to relay client %s on %s . ' ,
2015-09-15 02:23:56 +02:00
irc . name , args [ ' text ' ] , irc . users [ source ] . nick ,
2015-09-19 19:32:45 +02:00
remoteirc . users [ real_target ] . nick , channel )
2015-09-15 02:23:56 +02:00
irc . msg ( kicker , " This channel is claimed; your kick to "
" %s has been blocked because you are not "
" (half)opped. " % channel , notice = True )
else :
log . info ( ' ( %s ) Relay claim: Blocked KICK (reason %r ) from server %s to relay client %s / %s on %s . ' ,
irc . name , args [ ' text ' ] , irc . servers [ source ] . name ,
remoteirc . users [ real_target ] . nick , remoteirc . name , channel )
return
if not real_target :
continue
# Propogate the kick!
if real_kicker :
log . debug ( ' ( %s ) Relay kick: Kicking %s from channel %s via %s on behalf of %s / %s ' , irc . name , real_target , remotechan , real_kicker , kicker , irc . name )
2016-01-17 01:59:01 +01:00
remoteirc . proto . kick ( real_kicker , remotechan , real_target , args [ ' text ' ] )
2015-09-15 02:23:56 +02:00
else :
# Kick originated from a server, or the kicker isn't in any
# common channels with the target relay network.
rsid = getRemoteSid ( remoteirc , irc )
log . debug ( ' ( %s ) Relay kick: Kicking %s from channel %s via %s on behalf of %s / %s ' , irc . name , real_target , remotechan , rsid , kicker , irc . name )
try :
if kicker in irc . servers :
kname = irc . servers [ kicker ] . name
else :
kname = irc . users . get ( kicker ) . nick
2015-09-21 03:13:39 +02:00
text = " ( %s / %s ) %s " % ( kname , irc . name , args [ ' text ' ] )
2015-09-15 02:23:56 +02:00
except AttributeError :
2015-09-21 03:13:39 +02:00
text = " (<unknown kicker>@ %s ) %s " % ( irc . name , args [ ' text ' ] )
2016-01-17 01:59:01 +01:00
remoteirc . proto . kick ( rsid , remotechan , real_target , text )
2015-09-15 02:23:56 +02:00
# If the target isn't on any channels, quit them.
2015-10-18 19:25:59 +02:00
if remoteirc != irc and ( not remoteirc . users [ real_target ] . channels ) and not origuser :
del relayusers [ ( irc . name , target ) ] [ remoteirc . name ]
2016-01-17 01:51:54 +01:00
remoteirc . proto . quit ( real_target , ' Left all shared channels. ' )
2015-09-15 02:23:56 +02:00
if origuser and not irc . users [ target ] . channels :
del relayusers [ origuser ] [ irc . name ]
2016-01-17 01:51:54 +01:00
irc . proto . quit ( target , ' Left all shared channels. ' )
2015-09-15 02:23:56 +02:00
utils . add_hook ( handle_kick , ' KICK ' )
def handle_chgclient ( irc , source , command , args ) :
target = args [ ' target ' ]
if args . get ( ' newhost ' ) :
field = ' HOST '
text = args [ ' newhost ' ]
elif args . get ( ' newident ' ) :
field = ' IDENT '
text = args [ ' newident ' ]
elif args . get ( ' newgecos ' ) :
field = ' GECOS '
text = args [ ' newgecos ' ]
if field :
for netname , user in relayusers [ ( irc . name , target ) ] . items ( ) :
remoteirc = world . networkobjects [ netname ]
try :
2015-11-22 08:37:19 +01:00
if field == ' HOST ' :
text = normalizeHost ( remoteirc , text )
2015-09-15 02:23:56 +02:00
remoteirc . proto . updateClient ( user , field , text )
except NotImplementedError : # IRCd doesn't support changing the field we want
log . debug ( ' ( %s ) Ignoring changing field %r of %s on %s (for %s / %s ); '
' remote IRCd doesn \' t support it ' , irc . name , field ,
2015-12-31 00:53:05 +01:00
user , netname , target , irc . name )
2015-09-15 02:23:56 +02:00
continue
for c in ( ' CHGHOST ' , ' CHGNAME ' , ' CHGIDENT ' ) :
utils . add_hook ( handle_chgclient , c )
2015-07-20 07:43:26 +02:00
2015-07-15 04:39:49 +02:00
def handle_mode ( irc , numeric , command , args ) :
target = args [ ' target ' ]
modes = args [ ' modes ' ]
2015-08-29 18:39:33 +02:00
for name , remoteirc in world . networkobjects . items ( ) :
2015-07-26 07:56:34 +02:00
if irc . name == name or not remoteirc . connected . is_set ( ) :
2015-07-15 04:39:49 +02:00
continue
2015-07-20 07:43:26 +02:00
if utils . isChannel ( target ) :
2015-09-15 02:36:41 +02:00
oldchan = args . get ( ' oldchan ' )
2015-09-13 22:48:14 +02:00
if checkClaim ( irc , target , numeric , chanobj = oldchan ) :
2015-09-13 08:36:52 +02:00
relayModes ( irc , remoteirc , numeric , target , modes )
else : # Mode change blocked by CLAIM.
2015-09-13 22:48:14 +02:00
reversed_modes = utils . reverseModes ( irc , target , modes , oldobj = oldchan )
2015-09-13 08:36:52 +02:00
log . debug ( ' ( %s ) Reversing mode changes of %r with %r (CLAIM). ' ,
irc . name , modes , reversed_modes )
2016-01-17 02:08:17 +01:00
irc . proto . mode ( irc . pseudoclient . uid , target , reversed_modes )
2015-09-13 08:36:52 +02:00
break
2015-07-20 07:43:26 +02:00
else :
2015-08-31 23:23:42 +02:00
# Set hideoper on remote opers, to prevent inflating
# /lusers and various /stats
hideoper_mode = remoteirc . umodes . get ( ' hideoper ' )
2015-07-20 07:43:26 +02:00
modes = getSupportedUmodes ( irc , remoteirc , modes )
2015-08-31 23:23:42 +02:00
if hideoper_mode :
if ( ' +o ' , None ) in modes :
modes . append ( ( ' + %s ' % hideoper_mode , None ) )
elif ( ' -o ' , None ) in modes :
modes . append ( ( ' - %s ' % hideoper_mode , None ) )
2015-07-20 08:49:50 +02:00
remoteuser = getRemoteUser ( irc , remoteirc , target , spawnIfMissing = False )
2015-09-02 07:13:29 +02:00
if remoteuser and modes :
2016-01-17 02:08:17 +01:00
remoteirc . proto . mode ( remoteuser , remoteuser , modes )
2015-07-20 07:43:26 +02:00
2015-07-15 04:39:49 +02:00
utils . add_hook ( handle_mode , ' MODE ' )
2015-07-15 08:24:21 +02:00
def handle_topic ( irc , numeric , command , args ) :
channel = args [ ' channel ' ]
2015-09-14 02:58:39 +02:00
oldtopic = args . get ( ' oldtopic ' )
2015-12-19 06:53:35 +01:00
topic = args [ ' text ' ]
2015-09-13 23:23:27 +02:00
if checkClaim ( irc , channel , numeric ) :
for name , remoteirc in world . networkobjects . items ( ) :
if irc . name == name or not remoteirc . connected . is_set ( ) :
continue
2015-09-15 02:29:37 +02:00
remotechan = getRemoteChan ( irc , remoteirc , channel )
2015-09-13 23:23:27 +02:00
# Don't send if the remote topic is the same as ours.
if remotechan is None or topic == remoteirc . channels [ remotechan ] . topic :
continue
# This might originate from a server too.
remoteuser = getRemoteUser ( irc , remoteirc , numeric , spawnIfMissing = False )
if remoteuser :
2016-01-17 02:09:52 +01:00
remoteirc . proto . topic ( remoteuser , remotechan , topic )
2015-09-13 23:23:27 +02:00
else :
rsid = getRemoteSid ( remoteirc , irc )
2016-01-17 02:09:52 +01:00
remoteirc . proto . topicBurst ( rsid , remotechan , topic )
2015-09-14 02:58:39 +02:00
elif oldtopic : # Topic change blocked by claim.
2016-01-17 02:09:52 +01:00
irc . proto . topic ( irc . pseudoclient . uid , channel , oldtopic )
2015-07-15 08:24:21 +02:00
utils . add_hook ( handle_topic , ' TOPIC ' )
2015-07-15 08:25:40 +02:00
def handle_kill ( irc , numeric , command , args ) :
target = args [ ' target ' ]
userdata = args [ ' userdata ' ]
2015-09-15 02:29:37 +02:00
realuser = getOrigUser ( irc , target ) or userdata . __dict__ . get ( ' remote ' )
2015-07-25 03:26:31 +02:00
log . debug ( ' ( %s ) relay handle_kill: realuser is %r ' , irc . name , realuser )
# Target user was remote:
if realuser and realuser [ 0 ] != irc . name :
# We don't allow killing over the relay, so we must respawn the affected
# client and rejoin it to its channels.
del relayusers [ realuser ] [ irc . name ]
2015-09-13 02:41:49 +02:00
if killcache . setdefault ( irc . name , 0 ) < = 5 :
remoteirc = world . networkobjects [ realuser [ 0 ] ]
2015-09-18 04:22:34 +02:00
for remotechan in remoteirc . users [ realuser [ 1 ] ] . channels :
2015-09-15 02:29:37 +02:00
localchan = getRemoteChan ( remoteirc , irc , remotechan )
2015-09-13 02:41:49 +02:00
if localchan :
2015-09-18 04:31:30 +02:00
modes = getPrefixModes ( remoteirc , irc , remotechan , realuser [ 1 ] )
2015-09-13 02:41:49 +02:00
log . debug ( ' ( %s ) relay handle_kill: userpair: %s , %s ' , irc . name , modes , realuser )
client = getRemoteUser ( remoteirc , irc , realuser [ 1 ] )
2016-01-17 01:53:46 +01:00
irc . proto . sjoin ( getRemoteSid ( irc , remoteirc ) , localchan , [ ( modes , client ) ] )
2015-09-13 02:41:49 +02:00
if userdata and numeric in irc . users :
log . info ( ' ( %s ) Relay claim: Blocked KILL (reason %r ) from %s to relay client %s / %s . ' ,
irc . name , args [ ' text ' ] , irc . users [ numeric ] . nick ,
remoteirc . users [ realuser [ 1 ] ] . nick , realuser [ 0 ] )
irc . msg ( numeric , " Your kill to %s has been blocked "
" because PyLink does not allow killing "
" users over the relay at this time. " % \
userdata . nick , notice = True )
else :
log . info ( ' ( %s ) Relay claim: Blocked KILL (reason %r ) from server %s to relay client %s / %s . ' ,
irc . name , args [ ' text ' ] , irc . servers [ numeric ] . name ,
remoteirc . users [ realuser [ 1 ] ] . nick , realuser [ 0 ] )
2015-08-23 06:43:25 +02:00
else :
2015-09-13 02:41:49 +02:00
log . error ( ' ( %s ) Too many kills received for target %s , aborting! ' ,
irc . name , userdata . nick )
2016-01-10 05:03:42 +01:00
irc . disconnect ( )
2015-09-13 02:41:49 +02:00
killcache [ irc . name ] + = 1
2015-07-25 03:26:31 +02:00
# Target user was local.
else :
# IMPORTANT: some IRCds (charybdis) don't send explicit QUIT messages
# for locally killed clients, while others (inspircd) do!
# If we receive a user object in 'userdata' instead of None, it means
# that the KILL hasn't been handled by a preceding QUIT message.
if userdata :
handle_quit ( irc , target , ' KILL ' , { ' text ' : args [ ' text ' ] } )
2015-09-15 02:23:56 +02:00
utils . add_hook ( handle_kill , ' KILL ' )
def handle_away ( irc , numeric , command , args ) :
for netname , user in relayusers [ ( irc . name , numeric ) ] . items ( ) :
remoteirc = world . networkobjects [ netname ]
2016-01-17 01:40:36 +01:00
remoteirc . proto . away ( user , args [ ' text ' ] )
2015-09-15 02:23:56 +02:00
utils . add_hook ( handle_away , ' AWAY ' )
def handle_spawnmain ( irc , numeric , command , args ) :
if args [ ' olduser ' ] :
# Kills to the main PyLink client force reinitialization; this makes sure
# it joins all the relay channels like it's supposed to.
initializeAll ( irc )
utils . add_hook ( handle_spawnmain , ' PYLINK_SPAWNMAIN ' )
def handle_invite ( irc , source , command , args ) :
target = args [ ' target ' ]
channel = args [ ' channel ' ]
if isRelayClient ( irc , target ) :
2015-09-15 02:29:37 +02:00
remotenet , remoteuser = getOrigUser ( irc , target )
2015-09-15 02:23:56 +02:00
remoteirc = world . networkobjects [ remotenet ]
2015-09-15 02:29:37 +02:00
remotechan = getRemoteChan ( irc , remoteirc , channel )
2015-09-15 02:23:56 +02:00
remotesource = getRemoteUser ( irc , remoteirc , source , spawnIfMissing = False )
if remotesource is None :
irc . msg ( source , ' Error: You must be in a common channel '
' with %s to invite them to channels. ' % \
irc . users [ target ] . nick ,
notice = True )
elif remotechan is None :
irc . msg ( source , ' Error: You cannot invite someone to a '
' channel not on their network! ' ,
notice = True )
else :
2016-01-17 01:38:27 +01:00
remoteirc . proto . invite ( remotesource , remoteuser ,
2015-09-15 02:23:56 +02:00
remotechan )
utils . add_hook ( handle_invite , ' INVITE ' )
2015-09-29 03:15:56 +02:00
def handle_endburst ( irc , numeric , command , args ) :
if numeric == irc . uplink :
initializeAll ( irc )
utils . add_hook ( handle_endburst , " ENDBURST " )
2015-09-15 02:23:56 +02:00
def handle_disconnect ( irc , numeric , command , args ) :
2015-10-11 00:34:57 +02:00
""" Handles IRC network disconnections (internal hook). """
# Quit all of our users' representations on other nets, and remove
# them from our relay clients index.
2015-09-15 02:23:56 +02:00
for k , v in relayusers . copy ( ) . items ( ) :
if irc . name in v :
del relayusers [ k ] [ irc . name ]
if k [ 0 ] == irc . name :
2015-09-29 03:21:52 +02:00
try :
2015-10-24 03:50:42 +02:00
handle_quit ( irc , k [ 1 ] , ' PYLINK_DISCONNECT ' , { ' text ' : ' Relay network lost connection. ' } )
2015-09-29 03:21:52 +02:00
del relayusers [ k ]
except KeyError :
pass
2015-10-11 00:34:57 +02:00
# SQUIT all relay pseudoservers spawned for us, and remove them
# from our relay subservers index.
2015-10-11 00:06:36 +02:00
for name , ircobj in world . networkobjects . copy ( ) . items ( ) :
2015-10-11 00:27:57 +02:00
if name != irc . name and ircobj . connected . is_set ( ) :
2015-10-11 00:34:57 +02:00
try :
rsid = relayservers [ ircobj . name ] [ irc . name ]
except KeyError :
continue
else :
2016-01-17 01:53:06 +01:00
ircobj . proto . squit ( ircobj . sid , rsid , text = ' Relay network lost connection. ' )
2015-09-19 07:11:27 +02:00
del relayservers [ name ] [ irc . name ]
try :
del relayservers [ irc . name ]
except KeyError :
pass
2015-08-15 14:54:18 +02:00
2015-09-15 02:23:56 +02:00
utils . add_hook ( handle_disconnect , " PYLINK_DISCONNECT " )
2015-07-13 08:28:54 +02:00
2015-09-15 02:23:56 +02:00
def handle_save ( irc , numeric , command , args ) :
target = args [ ' target ' ]
2015-09-15 02:29:37 +02:00
realuser = getOrigUser ( irc , target )
2015-09-15 02:23:56 +02:00
log . debug ( ' ( %s ) relay handle_save: %r got in a nick collision! Real user: %r ' ,
irc . name , target , realuser )
if isRelayClient ( irc , target ) and realuser :
# Nick collision!
# It's one of our relay clients; try to fix our nick to the next
# available normalized nick.
remotenet , remoteuser = realuser
remoteirc = world . networkobjects [ remotenet ]
nick = remoteirc . users [ remoteuser ] . nick
# Limit how many times we can attempt to fix our nick, to prevent
# floods and such.
if savecache . setdefault ( irc . name , 0 ) < = 5 :
newnick = normalizeNick ( irc , remotenet , nick )
log . info ( ' ( %s ) SAVE received for relay client %r ( %s ), fixing nick to %s ' ,
irc . name , target , nick , newnick )
2016-01-17 01:51:04 +01:00
irc . proto . nick ( target , newnick )
2015-09-15 02:23:56 +02:00
else :
log . warning ( ' ( %s ) SAVE received for relay client %r ( %s ), not '
' fixing nick again due to 5 failed attempts in '
' the last 10 seconds! ' , irc . name , target , nick )
savecache [ irc . name ] + = 1
else :
# Somebody else on the network (not a PyLink client) had a nick collision;
# relay this as a nick change appropriately.
handle_nick ( irc , target , ' SAVE ' , { ' oldnick ' : None , ' newnick ' : target } )
2015-07-14 08:29:20 +02:00
2015-09-15 02:23:56 +02:00
utils . add_hook ( handle_save , " SAVE " )
### PUBLIC COMMANDS
2015-07-13 04:03:18 +02:00
2015-07-11 05:26:46 +02:00
@utils.add_cmd
def create ( irc , source , args ) :
""" <channel>
Creates the channel < channel > over the relay . """
try :
2015-09-02 07:01:22 +02:00
channel = utils . toLower ( irc , args [ 0 ] )
2015-07-11 05:26:46 +02:00
except IndexError :
2015-10-24 03:29:10 +02:00
irc . reply ( " Error: Not enough arguments. Needs 1: channel. " )
2015-07-12 22:09:35 +02:00
return
2015-07-11 05:26:46 +02:00
if not utils . isChannel ( channel ) :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: Invalid channel %r . ' % channel )
2015-07-11 05:26:46 +02:00
return
2015-07-13 02:59:09 +02:00
if source not in irc . channels [ channel ] . users :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: You must be in %r to complete this operation. ' % channel )
2015-07-11 05:26:46 +02:00
return
2015-09-18 04:22:34 +02:00
utils . checkAuthenticated ( irc , source )
2015-11-29 06:09:16 +01:00
# Check to see whether the channel requested is already part of a different
# relay.
2015-09-18 04:25:51 +02:00
localentry = getRelay ( ( irc . name , channel ) )
2015-09-18 04:24:38 +02:00
if localentry :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: Channel %r is already part of a relay. ' % channel )
2015-09-18 04:24:38 +02:00
return
2015-11-29 06:09:16 +01:00
2015-11-29 06:18:30 +01:00
creator = utils . getHostmask ( irc , source )
2015-11-29 06:09:16 +01:00
# Create the relay database entry with the (network name, channel name)
# pair - this is just a dict with various keys.
db [ ( irc . name , channel ) ] = { ' claim ' : [ irc . name ] , ' links ' : set ( ) ,
2016-01-10 05:30:54 +01:00
' blocked_nets ' : set ( ) , ' creator ' : creator ,
' ts ' : time . time ( ) }
2015-11-29 06:18:30 +01:00
log . info ( ' ( %s ) relay: Channel %s created by %s . ' , irc . name , channel , creator )
2015-07-13 08:28:54 +02:00
initializeChannel ( irc , channel )
2015-10-24 03:29:10 +02:00
irc . reply ( ' Done. ' )
2015-07-11 05:26:46 +02:00
@utils.add_cmd
def destroy ( irc , source , args ) :
""" <channel>
2015-07-18 07:35:34 +02:00
Removes < channel > from the relay , delinking all networks linked to it . """
2015-07-11 05:26:46 +02:00
try :
2015-09-02 07:01:22 +02:00
channel = utils . toLower ( irc , args [ 0 ] )
2015-07-11 05:26:46 +02:00
except IndexError :
2015-10-24 03:29:10 +02:00
irc . reply ( " Error: Not enough arguments. Needs 1: channel. " )
2015-07-12 22:09:35 +02:00
return
2015-07-11 05:26:46 +02:00
if not utils . isChannel ( channel ) :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: Invalid channel %r . ' % channel )
2015-07-11 05:26:46 +02:00
return
2015-09-18 04:22:34 +02:00
utils . checkAuthenticated ( irc , source )
2015-07-11 05:26:46 +02:00
2015-07-14 08:29:20 +02:00
entry = ( irc . name , channel )
if entry in db :
for link in db [ entry ] [ ' links ' ] :
2015-08-29 18:39:33 +02:00
removeChannel ( world . networkobjects . get ( link [ 0 ] ) , link [ 1 ] )
2015-07-13 08:28:54 +02:00
removeChannel ( irc , channel )
2015-07-14 08:29:20 +02:00
del db [ entry ]
2015-10-24 03:29:10 +02:00
irc . reply ( ' Done. ' )
2015-11-29 06:18:30 +01:00
log . info ( ' ( %s ) relay: Channel %s destroyed by %s . ' , irc . name ,
channel , utils . getHostmask ( irc , source ) )
2015-07-11 05:26:46 +02:00
else :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: No such relay %r exists. ' % channel )
2015-07-13 02:59:09 +02:00
return
2015-07-11 05:26:46 +02:00
2015-07-13 02:59:09 +02:00
@utils.add_cmd
def link ( irc , source , args ) :
""" <remotenet> <channel> <local channel>
Links channel < channel > on < remotenet > over the relay to < local channel > .
2015-07-18 07:35:34 +02:00
If < local channel > is not specified , it defaults to the same name as < channel > . """
2015-07-13 02:59:09 +02:00
try :
2015-09-02 07:01:22 +02:00
channel = utils . toLower ( irc , args [ 1 ] )
2015-07-13 02:59:09 +02:00
remotenet = args [ 0 ] . lower ( )
except IndexError :
2015-10-24 03:29:10 +02:00
irc . reply ( " Error: Not enough arguments. Needs 2-3: remote netname, channel, local channel name (optional). " )
2015-07-13 02:59:09 +02:00
return
try :
2015-09-02 07:01:22 +02:00
localchan = utils . toLower ( irc , args [ 2 ] )
2015-07-13 02:59:09 +02:00
except IndexError :
localchan = channel
for c in ( channel , localchan ) :
if not utils . isChannel ( c ) :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: Invalid channel %r . ' % c )
2015-07-13 02:59:09 +02:00
return
if source not in irc . channels [ localchan ] . users :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: You must be in %r to complete this operation. ' % localchan )
2015-07-13 02:59:09 +02:00
return
2015-09-18 04:22:34 +02:00
utils . checkAuthenticated ( irc , source )
2015-08-29 18:39:33 +02:00
if remotenet not in world . networkobjects :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: No network named %r exists. ' % remotenet )
2015-07-13 02:59:09 +02:00
return
2015-09-15 02:29:37 +02:00
localentry = getRelay ( ( irc . name , localchan ) )
2015-07-14 07:54:51 +02:00
if localentry :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: Channel %r is already part of a relay. ' % localchan )
2015-07-13 02:59:09 +02:00
return
try :
entry = db [ ( remotenet , channel ) ]
except KeyError :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: No such relay %r exists. ' % channel )
2015-07-13 02:59:09 +02:00
return
else :
2015-08-26 05:18:14 +02:00
if irc . name in entry [ ' blocked_nets ' ] :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: Access denied (network is banned from linking to this channel). ' )
2015-08-26 05:18:14 +02:00
return
2015-07-14 07:54:51 +02:00
for link in entry [ ' links ' ] :
if link [ 0 ] == irc . name :
2015-11-29 06:18:30 +01:00
irc . reply ( " Error: Remote channel ' %s %s ' is already linked here "
" as %r . " % ( remotenet , channel , link [ 1 ] ) )
2015-07-14 07:54:51 +02:00
return
2015-07-13 02:59:09 +02:00
entry [ ' links ' ] . add ( ( irc . name , localchan ) )
2015-11-29 06:18:30 +01:00
log . info ( ' ( %s ) relay: Channel %s linked to %s %s by %s . ' , irc . name ,
localchan , remotenet , channel , utils . getHostmask ( irc , source ) )
2015-07-13 08:28:54 +02:00
initializeChannel ( irc , localchan )
2015-10-24 03:29:10 +02:00
irc . reply ( ' Done. ' )
2015-07-12 22:09:35 +02:00
2015-07-13 02:59:09 +02:00
@utils.add_cmd
def delink ( irc , source , args ) :
""" <local channel> [<network>]
2015-07-18 07:35:34 +02:00
Delinks channel < local channel > . < network > must and can only be specified if you are on the host network for < local channel > , and allows you to pick which network to delink .
To remove a relay entirely , use the ' destroy ' command instead . """
2015-07-13 02:59:09 +02:00
try :
2015-09-02 07:01:22 +02:00
channel = utils . toLower ( irc , args [ 0 ] )
2015-07-13 02:59:09 +02:00
except IndexError :
2015-10-24 03:29:10 +02:00
irc . reply ( " Error: Not enough arguments. Needs 1-2: channel, remote netname (optional). " )
2015-07-13 02:59:09 +02:00
return
try :
remotenet = args [ 1 ] . lower ( )
except IndexError :
remotenet = None
2015-09-18 04:22:34 +02:00
utils . checkAuthenticated ( irc , source )
2015-07-13 02:59:09 +02:00
if not utils . isChannel ( channel ) :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: Invalid channel %r . ' % channel )
2015-07-13 02:59:09 +02:00
return
2015-09-15 02:29:37 +02:00
entry = getRelay ( ( irc . name , channel ) )
2015-07-14 07:54:51 +02:00
if entry :
if entry [ 0 ] == irc . name : # We own this channel.
2015-07-26 01:56:20 +02:00
if not remotenet :
2015-10-24 03:29:10 +02:00
irc . reply ( " Error: You must select a network to "
2015-08-12 16:03:49 +02:00
" delink, or use the ' destroy ' command to remove "
" this relay entirely (it was created on the current "
" network). " )
2015-07-14 07:54:51 +02:00
return
else :
2015-07-15 20:47:06 +02:00
for link in db [ entry ] [ ' links ' ] . copy ( ) :
if link [ 0 ] == remotenet :
2015-08-29 18:39:33 +02:00
removeChannel ( world . networkobjects . get ( remotenet ) , link [ 1 ] )
2015-07-15 20:47:06 +02:00
db [ entry ] [ ' links ' ] . remove ( link )
2015-07-13 02:59:09 +02:00
else :
2015-07-14 07:54:51 +02:00
removeChannel ( irc , channel )
2015-07-14 08:29:20 +02:00
db [ entry ] [ ' links ' ] . remove ( ( irc . name , channel ) )
2015-10-24 03:29:10 +02:00
irc . reply ( ' Done. ' )
2015-11-29 06:18:30 +01:00
log . info ( ' ( %s ) relay: Channel %s delinked from %s %s by %s . ' , irc . name ,
channel , entry [ 0 ] , entry [ 1 ] , utils . getHostmask ( irc , source ) )
2015-07-13 02:59:09 +02:00
else :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: No such relay %r . ' % channel )
2015-07-13 02:59:09 +02:00
2015-07-18 07:00:25 +02:00
@utils.add_cmd
def linked ( irc , source , args ) :
2015-07-18 07:35:34 +02:00
""" takes no arguments.
Returns a list of channels shared across the relay . """
2015-08-29 18:39:33 +02:00
networks = list ( world . networkobjects . keys ( ) )
2015-07-18 07:00:25 +02:00
networks . remove ( irc . name )
s = ' Connected networks: \x02 %s \x02 %s ' % ( irc . name , ' ' . join ( networks ) )
2015-09-07 07:23:44 +02:00
irc . msg ( source , s )
2015-11-29 06:09:16 +01:00
# Sort the list of shared channels when displaying
for k , v in sorted ( db . items ( ) ) :
# Bold each network/channel name pair
2015-07-18 07:00:25 +02:00
s = ' \x02 %s %s \x02 ' % k
2015-09-19 07:05:51 +02:00
remoteirc = world . networkobjects . get ( k [ 0 ] )
2015-11-29 06:09:16 +01:00
channel = k [ 1 ] # Get the channel name from the network/channel pair
2015-09-19 07:05:51 +02:00
if remoteirc and channel in remoteirc . channels :
c = remoteirc . channels [ channel ]
if ( ' s ' , None ) in c . modes or ( ' p ' , None ) in c . modes :
2015-11-29 06:09:16 +01:00
# Only show secret channels to opers, and tag them with
# [secret].
2015-09-19 07:05:51 +02:00
if utils . isOper ( irc , source ) :
s + = ' \x02 [secret] \x02 '
else :
continue
2015-11-29 06:09:16 +01:00
if v [ ' links ' ] : # Join up and output all the linked channel names.
2015-07-18 07:00:25 +02:00
s + = ' ' . join ( [ ' ' . join ( link ) for link in v [ ' links ' ] ] )
2015-11-29 06:09:16 +01:00
else : # Unless it's empty; then, well... just say no relays yet.
2015-07-18 07:00:25 +02:00
s + = ' (no relays yet) '
2015-11-29 06:09:16 +01:00
2015-09-07 07:23:44 +02:00
irc . msg ( source , s )
2015-08-12 13:18:20 +02:00
2015-11-29 06:09:16 +01:00
if utils . isOper ( irc , source ) :
2016-01-10 05:30:54 +01:00
s = ' '
2015-11-29 06:09:16 +01:00
# If the caller is an oper, we can show the hostmasks of people
# that created all the available channels (Janus does this too!!)
2015-12-27 00:24:06 +01:00
creator = v . get ( ' creator ' )
if creator :
# But only if the value actually exists (old DBs will have it
# missing).
2016-01-10 05:30:54 +01:00
s + = ' by \x02 %s \x02 ' % creator
# Ditto for creation date
ts = v . get ( ' ts ' )
if ts :
s + = ' on %s ' % time . ctime ( ts )
if s :
irc . msg ( source , ' Channel created %s . ' % s )
2015-11-29 06:09:16 +01:00
2015-08-26 05:18:14 +02:00
@utils.add_cmd
def linkacl ( irc , source , args ) :
""" ALLOW|DENY|LIST <channel> <remotenet>
Allows blocking / unblocking certain networks from linking to a relay , based on a blacklist .
LINKACL LIST returns a list of blocked networks for a channel , while the ALLOW and DENY subcommands allow manipulating this blacklist . """
2015-08-26 05:55:39 +02:00
missingargs = " Error: Not enough arguments. Needs 2-3: subcommand (ALLOW/DENY/LIST), channel, remote network (for ALLOW/DENY). "
2015-09-18 04:22:34 +02:00
utils . checkAuthenticated ( irc , source )
2015-08-26 05:18:14 +02:00
try :
cmd = args [ 0 ] . lower ( )
2015-09-02 07:01:22 +02:00
channel = utils . toLower ( irc , args [ 1 ] )
2015-08-26 05:18:14 +02:00
except IndexError :
2015-10-24 03:29:10 +02:00
irc . reply ( missingargs )
2015-08-26 05:18:14 +02:00
return
if not utils . isChannel ( channel ) :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: Invalid channel %r . ' % channel )
2015-08-26 05:18:14 +02:00
return
2015-09-15 02:29:37 +02:00
relay = getRelay ( ( irc . name , channel ) )
2015-08-26 05:18:14 +02:00
if not relay :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: No such relay %r exists. ' % channel )
2015-08-26 05:18:14 +02:00
return
if cmd == ' list ' :
s = ' Blocked networks for \x02 %s \x02 : \x02 %s \x02 ' % ( channel , ' , ' . join ( db [ relay ] [ ' blocked_nets ' ] ) or ' (empty) ' )
2015-10-24 03:29:10 +02:00
irc . reply ( s )
2015-08-26 05:18:14 +02:00
return
try :
remotenet = args [ 2 ]
except IndexError :
2015-10-24 03:29:10 +02:00
irc . reply ( missingargs )
2015-08-26 05:18:14 +02:00
return
if cmd == ' deny ' :
db [ relay ] [ ' blocked_nets ' ] . add ( remotenet )
2015-10-24 03:29:10 +02:00
irc . reply ( ' Done. ' )
2015-08-26 05:18:14 +02:00
elif cmd == ' allow ' :
try :
db [ relay ] [ ' blocked_nets ' ] . remove ( remotenet )
except KeyError :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: Network %r is not on the blacklist for %r . ' % ( remotenet , channel ) )
2015-08-26 05:18:14 +02:00
else :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Done. ' )
2015-08-26 05:18:14 +02:00
else :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: Unknown subcommand %r : valid ones are ALLOW, DENY, and LIST. ' % cmd )
2015-08-30 04:29:05 +02:00
@utils.add_cmd
def showuser ( irc , source , args ) :
""" <user>
2015-09-19 19:39:17 +02:00
Shows relay data about user < user > . This supplements the ' showuser ' command in the ' commands ' plugin , which provides more general information . """
2015-08-30 04:29:05 +02:00
try :
target = args [ 0 ]
except IndexError :
# No errors here; showuser from the commands plugin already does this
# for us.
return
2016-01-01 02:28:47 +01:00
u = irc . nickToUid ( target )
2015-08-30 04:49:37 +02:00
if u :
2015-08-30 04:29:05 +02:00
try :
2015-09-15 02:29:37 +02:00
userpair = getOrigUser ( irc , u ) or ( irc . name , u )
2015-08-30 04:29:05 +02:00
remoteusers = relayusers [ userpair ] . items ( )
except KeyError :
pass
else :
nicks = [ ]
if remoteusers :
2015-09-13 01:03:59 +02:00
nicks . append ( ' %s : \x02 %s \x02 ' % ( userpair [ 0 ] ,
2015-08-30 04:49:37 +02:00
world . networkobjects [ userpair [ 0 ] ] . users [ userpair [ 1 ] ] . nick ) )
2015-08-30 04:29:05 +02:00
for r in remoteusers :
remotenet , remoteuser = r
remoteirc = world . networkobjects [ remotenet ]
2015-09-13 01:03:59 +02:00
nicks . append ( ' %s : \x02 %s \x02 ' % ( remotenet , remoteirc . users [ remoteuser ] . nick ) )
2015-09-07 07:23:44 +02:00
irc . msg ( source , " \x02 Relay nicks \x02 : %s " % ' , ' . join ( nicks ) )
2015-08-30 04:29:05 +02:00
relaychannels = [ ]
for ch in irc . users [ u ] . channels :
2015-09-15 02:29:37 +02:00
relay = getRelay ( ( irc . name , ch ) )
2015-08-30 04:29:05 +02:00
if relay :
relaychannels . append ( ' ' . join ( relay ) )
2015-08-30 04:49:37 +02:00
if relaychannels and ( utils . isOper ( irc , source ) or u == source ) :
2015-09-07 07:23:44 +02:00
irc . msg ( source , " \x02 Relay channels \x02 : %s " % ' ' . join ( relaychannels ) )
2015-09-15 02:23:56 +02:00
@utils.add_cmd
def save ( irc , source , args ) :
""" takes no arguments.
Saves the relay database to disk . """
2015-09-18 04:22:34 +02:00
utils . checkAuthenticated ( irc , source )
exportDB ( )
2015-10-24 03:29:10 +02:00
irc . reply ( ' Done. ' )
2015-09-17 05:59:32 +02:00
@utils.add_cmd
def claim ( irc , source , args ) :
""" <channel> [<comma separated list of networks>]
Sets the CLAIM for a channel to a case - sensitive list of networks . If no list of networks is given , shows which networks have claim over the channel . A single hyphen ( - ) can also be given as a list of networks to remove claim from the channel entirely .
CLAIM is a way of enforcing network ownership for a channel , similarly to Janus . Unless the list is empty , only networks on the CLAIM list for a channel ( plus the creating network ) are allowed to override kicks , mode changes , and topic changes in it - attempts from other networks ' opers to do this are simply blocked or reverted. " " "
utils . checkAuthenticated ( irc , source )
try :
channel = utils . toLower ( irc , args [ 0 ] )
except IndexError :
2015-10-24 03:29:10 +02:00
irc . reply ( " Error: Not enough arguments. Needs 1-2: channel, list of networks (optional). " )
2015-09-17 05:59:32 +02:00
return
# We override getRelay() here to limit the search to the current network.
relay = ( irc . name , channel )
if relay not in db :
2015-10-24 03:29:10 +02:00
irc . reply ( ' Error: No such relay %r exists. ' % channel )
2015-09-17 05:59:32 +02:00
return
claimed = db [ relay ] [ " claim " ]
try :
nets = args [ 1 ] . strip ( )
except IndexError : # No networks given.
2015-10-24 03:29:10 +02:00
irc . reply ( ' Channel \x02 %s \x02 is claimed by: %s ' %
2015-09-26 18:39:45 +02:00
( channel , ' , ' . join ( claimed ) or ' \x1D (none) \x1D ' ) )
2015-09-17 05:59:32 +02:00
else :
if nets == ' - ' or not nets :
claimed = set ( )
else :
claimed = set ( nets . split ( ' , ' ) )
db [ relay ] [ " claim " ] = claimed
2015-10-24 03:29:10 +02:00
irc . reply ( ' CLAIM for channel \x02 %s \x02 set to: %s ' %
2015-09-26 18:39:45 +02:00
( channel , ' , ' . join ( claimed ) or ' \x1D (none) \x1D ' ) )