#!/usr/bin/env python
+from __future__ import with_statement
"""
irkerd - a simple IRC multiplexer daemon
*not* have a leading '#' unless the channel name itself does.
Options: -d sets the debug-message level (probably only of interest to
-developers). The -V option prints the program version and exits.
+developers). -l sets a logfile to capture message traffic from
+channels. -n sets the nick and -p the nickserv password. The -V
+option prints the program version and exits.
Design and code by Eric S. Raymond <esr@thyrsus.com>. See the project
resource page at <http://www.catb.org/~esr/irker/>.
HOST = "localhost"
PORT = 6659
-NAMESTYLE = "irker%03d" # IRC nick template - must contain '%d'
XMIT_TTL = (3 * 60 * 60) # Time to live, seconds from last transmit
PING_TTL = (15 * 60) # Time to live, seconds from last PING
HANDSHAKE_TTL = 60 # Time to live, seconds from nick transmit
# No user-serviceable parts below this line
-version = "1.17"
+version = "1.18"
-import sys, getopt, urlparse, time, random, socket, signal
+import sys, getopt, urlparse, time, random, socket, signal, re
import threading, Queue, SocketServer
import irc.client, logging
try:
"Return a name for the nth server connection."
if n is None:
n = self.nick_trial
- return (NAMESTYLE % n)
+ if fallback:
+ return (namestyle % n)
+ else:
+ return namestyle
def handle_ping(self):
"Register the fact that the server has pinged this connection."
self.last_ping = time.time()
"The server says we're OK, with a non-conflicting nick."
self.status = "ready"
self.irker.debug(1, "nick %s accepted" % self.nickname())
+ if password:
+ self.connection.privmsg("nickserv", "identify %s" % password)
def handle_badnick(self):
- "The server says our nick has a conflict."
+ "The server says our nick is ill-formed or has a conflict."
self.irker.debug(1, "nick %s rejected" % self.nickname())
- # Randomness prevents a malicious user or bot from anticipating the
- # next trial name in order to block us from completing the handshake.
- self.nick_trial += random.randint(1, 3)
- self.last_xmit = time.time()
- self.connection.nick(self.nickname())
+ if fallback:
+ # Randomness prevents a malicious user or bot from
+ # anticipating the next trial name in order to block us
+ # from completing the handshake.
+ self.nick_trial += random.randint(1, 3)
+ self.last_xmit = time.time()
+ self.connection.nick(self.nickname())
+ # Otherwise fall through, it might be possible to
+ # recover manually.
def handle_disconnect(self):
"Server disconnected us for flooding or some other reason."
self.connection = None
if channel not in self.channels_joined:
self.connection.join(channel)
self.irker.debug(1, "joining %s on %s." % (channel, self.servername))
- for segment in message.split("\n"):
- # Truncate the message if it's too long,
- # but we're working with characters here,
- # not bytes, so we could be off.
- # 500 = 512 - CRLF - 'PRIVMSG ' - ' :'
- maxlength = 500 - len(channel)
- if len(segment) > maxlength:
- segment = segment[:maxlength]
- try:
- self.connection.privmsg(channel, segment)
- except ValueError as err:
- self.irker.debug(1, "irclib rejected a message to %s on %s because: %s" % (channel, self.servername, str(err)))
- time.sleep(ANTI_FLOOD_DELAY)
+ # An empty message might be used as a keepalive or
+ # to join a channel for logging, so suppress the
+ # privmsg send unless there is actual traffic.
+ if message:
+ for segment in message.split("\n"):
+ # Truncate the message if it's too long,
+ # but we're working with characters here,
+ # not bytes, so we could be off.
+ # 500 = 512 - CRLF - 'PRIVMSG ' - ' :'
+ maxlength = 500 - len(channel)
+ if len(segment) > maxlength:
+ segment = segment[:maxlength]
+ try:
+ self.connection.privmsg(channel, segment)
+ except ValueError as err:
+ self.irker.debug(1, "irclib rejected a message to %s on %s because: %s" % (channel, self.servername, str(err)))
+ time.sleep(ANTI_FLOOD_DELAY)
self.last_xmit = self.channels_joined[channel] = time.time()
self.irker.debug(1, "XMIT_TTL bump (%s transmission) at %s" % (self.servername, time.asctime()))
self.queue.task_done()
self.debug(1, "irker has been kicked from %s on %s" % (target, connection.server))
if connection.context:
connection.context.handle_kick(target)
- def _handle_all_raw_messages(self, connection, event):
+ def _handle_all_raw_messages(self, _connection, event):
"Log all messages when in watcher mode."
- with open(logfile, "w") as logfp:
- logfp.write("%03f|%s|%s\n" % \
+ if logfile:
+ with open(logfile, "a") as logfp:
+ logfp.write("%03f|%s|%s\n" % \
(time.time(), event.source, event.arguments[0]))
def handle(self, line):
"Perform a JSON relay request."
if __name__ == '__main__':
debuglvl = 0
+ namestyle = "irker%03d"
+ password = None
logfile = None
- (options, arguments) = getopt.getopt(sys.argv[1:], "d:l:V:")
+ (options, arguments) = getopt.getopt(sys.argv[1:], "d:l:n:p:V:")
for (opt, val) in options:
if opt == '-d': # Enable debug/progress messages
debuglvl = int(val)
logging.basicConfig(level=logging.DEBUG)
elif opt == '-l': # Logfile mode - report traffic read in
logfile = val
+ elif opt == '-n': # Force the nick
+ namestyle = val
+ elif opt == '-p': # Set a nickserv password
+ password = val
elif opt == '-V': # Emit version and exit
sys.stdout.write("irkerd version %s\n" % version)
sys.exit(0)
+ fallback = re.search("%.*d", namestyle)
irker = Irker(debuglevel=debuglvl)
irker.debug(1, "irkerd version %s" % version)
try: