Version bump for release.
[irker.git] / irkerd
diff --git a/irkerd b/irkerd
index adcde8bad0ca5f52a1e87c52a2d00e18a74d5f22..d9505b378ae1bfa0ce7c48f15b5b8f848ae9cf5b 100755 (executable)
--- a/irkerd
+++ b/irkerd
@@ -40,11 +40,13 @@ CONNECTION_MAX = 200                # To avoid hitting a thread limit
 
 # 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
@@ -112,7 +114,7 @@ except NameError:  # Python 3
 # 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.
 #
@@ -132,7 +134,7 @@ class IRCError(Exception):
     pass
 
 
-class InvalidRequest (ValueError):
+class InvalidRequest(ValueError):
     "An invalid JSON request"
     pass
 
@@ -231,13 +233,13 @@ class IRCServerConnection():
         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
@@ -248,7 +250,7 @@ class IRCServerConnection():
             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):
@@ -263,8 +265,8 @@ class IRCServerConnection():
             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:
@@ -287,10 +289,12 @@ class IRCServerConnection():
 
         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):
@@ -452,7 +456,7 @@ class Connection:
         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):
@@ -567,8 +571,6 @@ class Connection:
                             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" % (
@@ -675,10 +677,10 @@ class Target():
             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,
@@ -697,7 +699,6 @@ class Target():
         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"
@@ -739,7 +740,7 @@ class Dispatcher:
                 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]
@@ -808,7 +809,7 @@ class Irker:
                     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:
@@ -934,6 +935,12 @@ class IrkerUDPHandler(socketserver.BaseRequestHandler):
             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(
@@ -941,9 +948,15 @@ if __name__ == '__main__':
     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')
@@ -954,14 +967,24 @@ if __name__ == '__main__':
         '-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())
@@ -973,17 +996,29 @@ if __name__ == '__main__':
         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)