--- /dev/null
+#! /usr/bin/python
+
+import sre as RX
+import os as OS
+import time as T
+import socket as S
+from getopt import getopt, GetoptError
+from sys import stdin, stdout, stderr, argv, exit
+from cStringIO import StringIO
+
+prog = argv[0]
+
+def bad(msg):
+ print >>stderr, '%s (fatal): %s' % (prog, msg)
+ exit(100)
+def die(msg):
+ print >>stderr, '%s: %s' % (prog, msg)
+ exit(111)
+def usage():
+ print >>stderr, \
+ ('Usage: %s [-d DIST] [-h HOST] [-r REMOTE] [-p PATH] GROUP <MESSAGE' %
+ prog)
+ exit(111)
+
+def headers(file):
+ h = None
+ while True:
+ line = file.next()
+ if line == '' or line == '\n':
+ break
+ if line[0].isspace():
+ if h is None:
+ bad('unexpected continuation')
+ h += line
+ else:
+ if h: yield h
+ h = line
+ if h: yield h
+
+def hdrsplit(h):
+ v = h.split(':', 1)
+ if len(v) != 2:
+ bad('failed to parse header')
+ return v[0].strip().lower(), v[1].strip()
+
+remote = ('localhost', 119)
+approved = None
+try:
+ host = OS.popen('hostname -f').read().strip()
+except:
+ host = 'localhost'
+dist = 'mail'
+path = 'newsgate'
+group = None
+
+def opts():
+ global approved, remote, host, dist, path, group
+ try:
+ opts, args = getopt(argv[1:], 'a:d:h:r:p:',
+ ['approved=', 'distribution=',
+ 'hostname=', 'remote=', 'path='])
+ except GetoptError:
+ usage()
+ for o, a in opts:
+ if o in ('-a', '--approved'):
+ approved = a
+ elif o in ('-d', '--distribution'):
+ dist = a
+ elif o in ('-h', '--hostname'):
+ host = a
+ elif o in ('-r', '--remote'):
+ remote = (lambda addr, port = 119: (addr, int(port)))(*a.split(':'))
+ if len(args) != 1:
+ usage()
+ group, = args
+
+rx_msgid = RX.compile(r'^\<\S+@\S+\>$')
+
+class NNTP (object):
+ def __init__(me, addr):
+ me.sk = S.socket(S.AF_INET, S.SOCK_STREAM)
+ me.sk.connect(remote)
+ me.f = me.sk.makefile()
+ rc, msg = me.reply()
+ if rc != '200':
+ die('unable to contact server: %s %s' % (rc, msg))
+ def write(me, stuff):
+ me.f.write(stuff)
+ def flush(me):
+ me.f.flush()
+ def cmd(me, stuff):
+ me.f.write(stuff + '\r\n')
+ me.f.flush()
+ def reply(me):
+ rc, msg = (lambda rc, msg = '.': (rc, msg.strip())) \
+ (*me.f.readline().split(None, 1))
+ if rc.startswith('5'):
+ die('server hated me: %s %s' % (rc, msg))
+ return rc, msg.strip()
+
+def send():
+ hdr = StringIO()
+ hdr.write('Path: newsgate\r\n'
+ 'Distribution: mail\r\n'
+ 'Newsgroups: %s\r\n'
+ 'Approved: %s\r\n'
+ % (group, approved or 'newsgate@%s' % host))
+ xify = {}
+ for h in '''
+ lines xref newsgroups path distribution approved received
+ '''.split():
+ xify[h] = 1
+ seen = {}
+ for h in headers(stdin):
+ n, c = hdrsplit(h)
+ if n in xify:
+ h = 'X-Newsgate-' + h
+ elif h.startswith('.'):
+ h = '.' + h
+ seen[n] = c
+ if h.endswith('\r\n'):
+ pass
+ elif h.endswith('\n'):
+ h = h[:-1] + '\r\n'
+ else:
+ h += '\r\n'
+ hdr.write(h)
+ if 'message-id' not in seen:
+ seen['message-id'] = ('<newsgate-%s@%s>'
+ % (OS.popen('gorp 128').read().strip(),
+ host))
+ hdr.write('Message-ID: %s\r\n' % seen['message-id'])
+ if 'date' not in seen:
+ hdr.write('Date: %s\r\n'
+ % (T.strftime('%a, %d %b %Y %H:%M:%S %Z')))
+ if 'subject' not in seen:
+ hdr.write('Subject: (no subject)\r\n')
+ hdr.write('\r\n')
+
+ msgid = seen['message-id']
+ if not rx_msgid.match(msgid):
+ bad('invalid message-id %s' % msgid)
+
+ nntp = NNTP(remote)
+ nntp.cmd('IHAVE %s' % msgid)
+ rc, msg = nntp.reply()
+ if rc == '335':
+ nntp.write(hdr.getvalue())
+ for i in stdin:
+ if i.startswith('.'):
+ i = '.' + i
+ if i.endswith('\r\n'):
+ pass
+ elif i.endswith('\n'):
+ i = i[:-1] + '\r\n'
+ else:
+ i = i + '\r\n'
+ nntp.write(i)
+ nntp.write('.\r\n')
+ nntp.flush()
+ rc, msg = nntp.reply()
+ if rc == '435':
+ ## doesn't want my article; pretend all is fine: I don't care
+ pass
+ elif rc == '436':
+ die('failed to send article: %s %s' % (rc, msg))
+ elif rc == '437':
+ bad('server rejected article: %s %s' % (rc, msg))
+ elif not rc.startswith('2'):
+ die('unexpected response from server: %s %s' % (rc, msg))
+ nntp.cmd('QUIT')
+ nntp.reply()
+
+def main():
+ try:
+ opts()
+ send()
+ except SystemExit:
+ raise
+# except Exception, exc:
+# die('unhandled exception: %s, %s' % (exc.__class__.__name__,
+# exc.args))
+main()