# No user-serviceable parts below this line
-version = "2.6"
+version = "2.9"
import argparse
import logging
+import logging.handlers
import json
+import os
try: # Python 3
import queue
except ImportError: # Python 2
# same problem - there is little point in reliable delivery to a relay
# that is down or unreliable.
#
-# This code uses only NICK, JOIN, PART, MODE, PRIVMSG, USER, and QUIT.
+# This code uses only NICK, JOIN, PART, MODE, PRIVMSG, USER, and QUIT.
# It is strictly compliant to RFC1459, except for the interpretation and
# use of the DEAF and CHANLIMIT and (obsolete) MAXCHANNELS features.
#
pass
-class InvalidRequest (ValueError):
+class InvalidRequest(ValueError):
"An invalid JSON request"
pass
self.master = master
self.socket = None
- def _wrap_socket(self, socket, target, cafile=None,
+ def _wrap_socket(self, socket, target, certfile=None, cafile=None,
protocol=ssl.PROTOCOL_TLSv1):
try: # Python 3.2 and greater
ssl_context = ssl.SSLContext(protocol)
except AttributeError: # Python < 3.2
self.socket = ssl.wrap_socket(
- self.socket, cert_reqs=ssl.CERT_REQUIRED,
+ socket, certfile=certfile, cert_reqs=ssl.CERT_REQUIRED,
ssl_version=protocol, ca_certs=cafile)
else:
ssl_context.verify_mode = ssl.CERT_REQUIRED
kwargs = {}
if ssl.HAS_SNI:
kwargs['server_hostname'] = target.servername
- self.socket = ssl_context.wrap_socket(self.socket, **kwargs)
+ self.socket = ssl_context.wrap_socket(socket, **kwargs)
return self.socket
def _check_hostname(self, target):
LOG.warning(
'cannot check SSL/TLS hostname with Python %s' % sys.version)
- def connect(self, target, nickname,
- password=None, username=None, ircname=None, **kwargs):
+ def connect(self, target, nickname, username=None, realname=None,
+ **kwargs):
LOG.debug("connect(server=%r, port=%r, nickname=%r, ...)" % (
target.servername, target.port, nickname))
if self.socket is not None:
if target.ssl:
self._check_hostname(target=target)
- if password:
- self.ship("PASS " + password)
+ if target.password:
+ self.ship("PASS " + target.password)
self.nick(self.nickname)
- self.user(username=username or ircname, realname=ircname or nickname)
+ self.user(
+ username=target.username or username or 'irker',
+ realname=realname or 'irker relaying client')
return self
def close(self):
if n is None:
n = self.nick_trial
if self.nick_needs_number:
- return (self.nick_template % n)
+ return self.nick_template % n
else:
return self.nick_template
def handle_ping(self):
self.connection.connect(
target=self.target,
nickname=self.nickname(),
- username="irker",
- ircname="irker relaying client",
**self.kwargs)
self.status = "handshaking"
LOG.info("XMIT_TTL bump (%s connection) at %s" % (
default_ircport = 6697
else:
default_ircport = 6667
- irchost, _, ircport = parsed.netloc.partition(':')
- if not ircport:
- ircport = default_ircport
- self.servername = irchost
+ self.username = parsed.username
+ self.password = parsed.password
+ self.servername = parsed.hostname
+ self.port = parsed.port or default_ircport
# IRC channel names are case-insensitive. If we don't smash
# case here we may run into problems later. There was a bug
# observed on irc.rizon.net where an irkerd user specified #Channel,
self.key = ""
if parsed.query:
self.key = re.sub("^key=", "", parsed.query)
- self.port = int(ircport)
def __str__(self):
"Represent this instance as a string"
if age < time.time() - CHANNEL_TTL:
ancients.append((connection, chan, age))
if ancients:
- ancients.sort(key=lambda x: x[2])
+ ancients.sort(key=lambda x: x[2])
(found_connection, drop_channel, _drop_age) = ancients[0]
found_connection.part(drop_channel, "scavenged by irkerd")
del found_connection.channels_joined[drop_channel]
m = int(lump[12:])
for pref in "#&+":
cxt.channel_limits[pref] = m
- LOG.info("%s maxchannels is %d" % (connection.server, m))
+ LOG.info("%s maxchannels is %d" % (connection.target, m))
elif lump.startswith("CHANLIMIT=#:"):
limits = lump[10:].split(",")
try:
line = UNICODE_TYPE(line, 'utf-8')
irker.handle(line=line.strip())
+def in_background():
+ "Is this process running in background?"
+ try:
+ return os.getpgrp() != os.tcgetpgrp(1)
+ except OSError:
+ return True
if __name__ == '__main__':
parser = argparse.ArgumentParser(
parser.add_argument(
'-c', '--ca-file', metavar='PATH',
help='file of trusted certificates for SSL/TLS')
+ parser.add_argument(
+ '-e', '--cert-file', metavar='PATH',
+ help='pem file used to authenticate to the server')
parser.add_argument(
'-d', '--log-level', metavar='LEVEL', choices=LOG_LEVELS,
- help='file of trusted certificates for SSL/TLS')
+ help='how much to log to the log file (one of %(choices)s)')
+ parser.add_argument(
+ '-H', '--host', metavar='ADDRESS', default=HOST,
+ help='IP address to listen on')
parser.add_argument(
'-l', '--log-file', metavar='PATH',
help='file for saving captured message traffic')
'-p', '--password', metavar='PASSWORD',
help='NickServ password')
parser.add_argument(
- '-i', '--immediate', action='store_const', const=True,
- help='disconnect after sending each message')
+ '-i', '--immediate', metavar='IRC-URL',
+ help=(
+ 'send a single message to IRC-URL and exit. The message is the '
+ 'first positional argument.'))
parser.add_argument(
'-V', '--version', action='version',
version='%(prog)s {0}'.format(version))
+ parser.add_argument(
+ 'message', metavar='MESSAGE', nargs='?',
+ help='message for --immediate mode')
args = parser.parse_args()
- handler = logging.StreamHandler()
+ if not args.log_file and in_background():
+ handler = logging.handlers.SysLogHandler(address='/dev/log',
+ facility='daemon')
+ else:
+ handler = logging.StreamHandler()
+
LOG.addHandler(handler)
if args.log_level:
log_level = getattr(logging, args.log_level.upper())
nick_needs_number=re.search('%.*d', args.nick),
password=args.password,
cafile=args.ca_file,
+ certfile=args.cert_file,
)
LOG.info("irkerd version %s" % version)
if args.immediate:
+ if not args.message:
+ LOG.error(
+ '--immediate set (%r), but message argument not given' % (
+ args.immediate))
+ raise SystemExit(1)
irker.irc.add_event_handler("quit", lambda _c, _e: sys.exit(0))
- irker.handle('{"to":"%s","privmsg":"%s"}' % (immediate, arguments[0]), quit_after=True)
+ irker.handle('{"to":"%s","privmsg":"%s"}' % (
+ args.immediate, args.message), quit_after=True)
irker.irc.spin()
else:
+ if args.message:
+ LOG.error(
+ 'message argument given (%r), but --immediate not set' % (
+ args.message))
+ raise SystemExit(1)
irker.thread_launch()
try:
- tcpserver = socketserver.TCPServer((HOST, PORT), IrkerTCPHandler)
- udpserver = socketserver.UDPServer((HOST, PORT), IrkerUDPHandler)
+ tcpserver = socketserver.TCPServer((args.host, PORT), IrkerTCPHandler)
+ udpserver = socketserver.UDPServer((args.host, PORT), IrkerUDPHandler)
for server in [tcpserver, udpserver]:
server = threading.Thread(target=server.serve_forever)
server.setDaemon(True)