mirror of
https://github.com/jlu5/PyLink.git
synced 2024-11-27 21:19:31 +01:00
commit
d3e335edee
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ __pycache__/
|
||||
*.save*
|
||||
*.db
|
||||
*.pid
|
||||
*.pem
|
||||
|
@ -21,6 +21,7 @@ class IrcUser():
|
||||
|
||||
self.identified = False
|
||||
self.channels = set()
|
||||
self.away = ''
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.__dict__)
|
||||
|
@ -53,6 +53,17 @@ servers:
|
||||
# PyLink might introduce a nick that is too long and cause netsplits!
|
||||
maxnicklen: 30
|
||||
|
||||
# Toggles SSL for this network. Defaults to false if not specified, and requires the
|
||||
# ssl_certfile and ssl_keyfile options to work.
|
||||
# ssl: true
|
||||
|
||||
# ssl_certfile: pylink-cert.pem
|
||||
# ssl_keyfile: pylink-key.pem
|
||||
|
||||
# Optionally, you can set this option to verify the SSL certificate
|
||||
# fingerprint (SHA1) of your uplink.
|
||||
# ssl_fingerprint: "e0fee1adf795c84eec4735f039503eb18d9c35cc"
|
||||
|
||||
ts6net:
|
||||
ip: 127.0.0.1
|
||||
port: 7000
|
||||
|
77
main.py
77
main.py
@ -7,6 +7,8 @@ import time
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
import threading
|
||||
import ssl
|
||||
import hashlib
|
||||
|
||||
from log import log
|
||||
import conf
|
||||
@ -73,20 +75,73 @@ class Irc():
|
||||
ip = self.serverdata["ip"]
|
||||
port = self.serverdata["port"]
|
||||
while True:
|
||||
log.info("Connecting to network %r on %s:%s", self.name, ip, port)
|
||||
self.initVars()
|
||||
checks_ok = True
|
||||
try:
|
||||
self.socket = socket.socket()
|
||||
self.socket.setblocking(0)
|
||||
# Initial connection timeout is a lot smaller than the timeout after
|
||||
# we've connected; this is intentional.
|
||||
self.socket = socket.create_connection((ip, port), timeout=self.pingfreq)
|
||||
self.socket.setblocking(0)
|
||||
self.socket.settimeout(self.pingfreq)
|
||||
self.ssl = self.serverdata.get('ssl')
|
||||
if self.ssl:
|
||||
log.info('(%s) Attempting SSL for this connection...', self.name)
|
||||
certfile = self.serverdata.get('ssl_certfile')
|
||||
keyfile = self.serverdata.get('ssl_keyfile')
|
||||
if certfile and keyfile:
|
||||
try:
|
||||
self.socket = ssl.wrap_socket(self.socket,
|
||||
certfile=certfile,
|
||||
keyfile=keyfile)
|
||||
except OSError:
|
||||
log.exception('(%s) Caught OSError trying to '
|
||||
'initialize the SSL connection; '
|
||||
'are "ssl_certfile" and '
|
||||
'"ssl_keyfile" set correctly?',
|
||||
self.name)
|
||||
checks_ok = False
|
||||
else:
|
||||
log.error('(%s) SSL certfile/keyfile was not set '
|
||||
'correctly, aborting... ', self.name)
|
||||
checks_ok = False
|
||||
log.info("Connecting to network %r on %s:%s", self.name, ip, port)
|
||||
self.socket.connect((ip, port))
|
||||
self.socket.settimeout(self.pingtimeout)
|
||||
self.proto.connect(self)
|
||||
self.spawnMain()
|
||||
log.info('(%s) Starting ping schedulers....', self.name)
|
||||
self.schedulePing()
|
||||
log.info('(%s) Server ready; listening for data.', self.name)
|
||||
self.run()
|
||||
|
||||
if self.ssl and checks_ok:
|
||||
peercert = self.socket.getpeercert(binary_form=True)
|
||||
sha1fp = hashlib.sha1(peercert).hexdigest()
|
||||
expected_fp = self.serverdata.get('ssl_fingerprint')
|
||||
if expected_fp:
|
||||
if sha1fp != expected_fp:
|
||||
log.error('(%s) Uplink\'s SSL certificate '
|
||||
'fingerprint (SHA1) does not match the '
|
||||
'one configured: expected %r, got %r; '
|
||||
'disconnecting...', self.name,
|
||||
expected_fp, sha1fp)
|
||||
checks_ok = False
|
||||
else:
|
||||
log.info('(%s) Uplink SSL certificate fingerprint '
|
||||
'(SHA1) verified: %r', self.name, sha1fp)
|
||||
else:
|
||||
log.info('(%s) Uplink\'s SSL certificate fingerprint '
|
||||
'is %r. You can enhance the security of your '
|
||||
'link by specifying this in a "ssl_fingerprint"'
|
||||
' option in your server block.', self.name,
|
||||
sha1fp)
|
||||
|
||||
if checks_ok:
|
||||
self.proto.connect(self)
|
||||
self.spawnMain()
|
||||
log.info('(%s) Starting ping schedulers....', self.name)
|
||||
self.schedulePing()
|
||||
log.info('(%s) Server ready; listening for data.', self.name)
|
||||
self.run()
|
||||
else:
|
||||
log.error('(%s) A configuration error was encountered '
|
||||
'trying to set up this connection. Please check'
|
||||
' your configuration file and try again.',
|
||||
self.name)
|
||||
except (socket.error, classes.ProtocolError, ConnectionError) as e:
|
||||
log.warning('(%s) Disconnected from IRC: %s: %s',
|
||||
self.name, type(e).__name__, str(e))
|
||||
@ -117,8 +172,8 @@ class Irc():
|
||||
log.debug('(%s) self.pingtimeout: %s', self.name, self.pingtimeout)
|
||||
data = self.socket.recv(2048)
|
||||
buf += data
|
||||
if self.connected and not data:
|
||||
log.warn('(%s) No data received and self.connected is not set; disconnecting!', self.name)
|
||||
if self.connected.is_set() and not data:
|
||||
log.warn('(%s) No data received and self.connected is set; disconnecting!', self.name)
|
||||
break
|
||||
while b'\n' in buf:
|
||||
line, buf = buf.split(b'\n', 1)
|
||||
|
@ -109,7 +109,8 @@ def getPrefixModes(irc, remoteirc, channel, user):
|
||||
for pmode in ('owner', 'admin', 'op', 'halfop', 'voice'):
|
||||
if pmode in remoteirc.cmodes: # Mode supported by IRCd
|
||||
mlist = irc.channels[channel].prefixmodes[pmode+'s']
|
||||
log.debug('(%s) getPrefixModes: checking if %r is in %r', irc.name, user, mlist)
|
||||
log.debug('(%s) getPrefixModes: checking if %r is in %s list: %r',
|
||||
irc.name, user, pmode, mlist)
|
||||
if user in mlist:
|
||||
modes += remoteirc.cmodes[pmode]
|
||||
return modes
|
||||
@ -142,6 +143,9 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
|
||||
host=host, realname=realname,
|
||||
modes=modes, ts=userobj.ts).uid
|
||||
remoteirc.users[u].remote = irc.name
|
||||
away = userobj.away
|
||||
if away:
|
||||
remoteirc.proto.awayClient(remoteirc, u, away)
|
||||
relayusers[(irc.name, user)][remoteirc.name] = u
|
||||
return u
|
||||
|
||||
@ -158,11 +162,9 @@ def getLocalUser(irc, user, targetirc=None):
|
||||
# First, iterate over everyone!
|
||||
remoteuser = None
|
||||
for k, v in relayusers.items():
|
||||
log.debug('(%s) getLocalUser: processing %s, %s in relayusers', irc.name, k, v)
|
||||
if k[0] == irc.name:
|
||||
# We don't need to do anything if the target users is on
|
||||
# the same network as us.
|
||||
log.debug('(%s) getLocalUser: skipping %s since the target network matches the source network.', irc.name, k)
|
||||
continue
|
||||
if v.get(irc.name) == user:
|
||||
# If the stored pseudoclient UID for the kicked user on
|
||||
@ -718,9 +720,9 @@ def removeChannel(irc, channel):
|
||||
irc.proto.partClient(irc, user, channel, 'Channel delinked.')
|
||||
# Don't ever quit it either...
|
||||
if user != irc.pseudoclient.uid and not irc.users[user].channels:
|
||||
irc.proto.quitClient(irc, user, 'Left all shared channels.')
|
||||
remoteuser = getLocalUser(irc, user)
|
||||
del relayusers[remoteuser][irc.name]
|
||||
irc.proto.quitClient(irc, user, 'Left all shared channels.')
|
||||
|
||||
@utils.add_cmd
|
||||
def create(irc, source, args):
|
||||
@ -847,7 +849,10 @@ def delink(irc, source, args):
|
||||
if entry:
|
||||
if entry[0] == irc.name: # We own this channel.
|
||||
if not remotenet:
|
||||
utils.msg(irc, source, "Error: you must select a network to delink, or use the 'destroy' command no remove this relay entirely.")
|
||||
utils.msg(irc, source, "Error: You must select a network to "
|
||||
"delink, or use the 'destroy' command to remove "
|
||||
"this relay entirely (it was created on the current "
|
||||
"network).")
|
||||
return
|
||||
else:
|
||||
for link in db[entry]['links'].copy():
|
||||
@ -933,3 +938,9 @@ def linked(irc, source, args):
|
||||
else:
|
||||
s += '(no relays yet)'
|
||||
utils.msg(irc, source, s)
|
||||
|
||||
def handle_away(irc, numeric, command, args):
|
||||
for netname, user in relayusers[(irc.name, numeric)].items():
|
||||
remoteirc = utils.networkobjects[netname]
|
||||
remoteirc.proto.awayClient(remoteirc, user, args['text'])
|
||||
utils.add_hook(handle_away, 'AWAY')
|
||||
|
@ -80,7 +80,8 @@ def sjoinServer(irc, server, channel, users, ts=None):
|
||||
else:
|
||||
utils.applyModes(irc, channel, modes)
|
||||
'''
|
||||
modes = irc.channels[channel].modes
|
||||
# Strip out list-modes, they shouldn't be ever sent in FJOIN.
|
||||
modes = [m for m in irc.channels[channel].modes if m[0] not in irc.cmodes['*A']]
|
||||
uids = []
|
||||
changedmodes = []
|
||||
namelist = []
|
||||
@ -297,6 +298,16 @@ def numericServer(irc, source, numeric, text):
|
||||
"locally by InspIRCd servers, so there is no "
|
||||
"need for PyLink to send numerics directly yet.")
|
||||
|
||||
def awayClient(irc, source, text):
|
||||
"""<irc object> <numeric> <text>
|
||||
|
||||
Sends an AWAY message with text <text> from PyLink client <numeric>.
|
||||
<text> can be an empty string to unset AWAY status."""
|
||||
if text:
|
||||
_send(irc, source, 'AWAY %s :%s' % (int(time.time()), text))
|
||||
else:
|
||||
_send(irc, source, 'AWAY')
|
||||
|
||||
def connect(irc):
|
||||
ts = irc.start_ts
|
||||
|
||||
@ -664,3 +675,13 @@ def handle_fname(irc, numeric, command, args):
|
||||
|
||||
def handle_endburst(irc, numeric, command, args):
|
||||
return {}
|
||||
|
||||
def handle_away(irc, numeric, command, args):
|
||||
# <- :1MLAAAAIG AWAY 1439371390 :Auto-away
|
||||
try:
|
||||
ts = args[0]
|
||||
irc.users[numeric].away = text = args[1]
|
||||
return {'text': text, 'ts': ts}
|
||||
except IndexError: # User is unsetting away status
|
||||
irc.users[numeric].away = ''
|
||||
return {'text': ''}
|
||||
|
@ -231,6 +231,16 @@ def pingServer(irc, source=None, target=None):
|
||||
def numericServer(irc, source, numeric, target, text):
|
||||
_send(irc, source, '%s %s %s' % (numeric, target, text))
|
||||
|
||||
def awayClient(irc, source, text):
|
||||
"""<irc object> <numeric> <text>
|
||||
|
||||
Sends an AWAY message with text <text> from PyLink client <numeric>.
|
||||
<text> can be an empty string to unset AWAY status."""
|
||||
if text:
|
||||
_send(irc, source, 'AWAY :%s' % text)
|
||||
else:
|
||||
_send(irc, source, 'AWAY')
|
||||
|
||||
def connect(irc):
|
||||
ts = irc.start_ts
|
||||
|
||||
@ -649,3 +659,13 @@ def handle_472(irc, numeric, command, args):
|
||||
' desyncs, try adding the line "loadmodule "extensions/%s.so";" to '
|
||||
'your IRCd configuration.', irc.name, setter, badmode,
|
||||
charlist[badmode])
|
||||
|
||||
def handle_away(irc, numeric, command, args):
|
||||
# <- :6ELAAAAAB AWAY :Auto-away
|
||||
|
||||
try:
|
||||
irc.users[numeric].away = text = args[0]
|
||||
except IndexError: # User is unsetting away status
|
||||
irc.users[numeric].away = text = ''
|
||||
return {'text': text}
|
||||
|
||||
|
@ -1,18 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# Shell script to start PyLink under CPUlimit, killing it if it starts abusing the CPU.
|
||||
# Shell script to start PyLink under CPUlimit, throttling it if it starts abusing the CPU.
|
||||
|
||||
# Set this to whatever you want. cpulimit --help
|
||||
LIMIT=20
|
||||
LIMIT=35
|
||||
|
||||
# Change to the PyLink root directory.
|
||||
WRAPPER_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
cd "$WRAPPER_DIR"
|
||||
|
||||
if [[ ! -z "$(which cpulimit)" ]]; then
|
||||
# -k kills the PyLink daemon if it goes over $LIMIT
|
||||
# -z makes cpulimit exit when PyLink dies.
|
||||
cpulimit -l $LIMIT -z -k ./main.py
|
||||
echo "PyLink has been started (daemonized) under cpulimit, and will automatically be killed if it goes over the CPU limit of ${LIMIT}%."
|
||||
cpulimit -l $LIMIT -z ./main.py
|
||||
echo "PyLink has been started (daemonized) under cpulimit, and will automatically be throttled if it goes over the CPU limit of ${LIMIT}%."
|
||||
echo "To kill the process manually, run ./kill.sh"
|
||||
else
|
||||
echo 'cpulimit not found in $PATH! Aborting.'
|
||||
|
Loading…
Reference in New Issue
Block a user