chiark / gitweb /
inject: New posting system.
[newsgate] / bin / inject
CommitLineData
4aef1ddb
MW
1#! /usr/bin/python
2
3import sre as RX
4import os as OS
5import time as T
6import socket as S
7from getopt import getopt, GetoptError
8from sys import stdin, stdout, stderr, argv, exit
9from cStringIO import StringIO
10
11prog = argv[0]
12
13def bad(msg):
14 print >>stderr, '%s (fatal): %s' % (prog, msg)
15 exit(100)
16def die(msg):
17 print >>stderr, '%s: %s' % (prog, msg)
18 exit(111)
19def usage():
20 print >>stderr, \
21 ('Usage: %s [-d DIST] [-h HOST] [-r REMOTE] [-p PATH] GROUP <MESSAGE' %
22 prog)
23 exit(111)
24
25def headers(file):
26 h = None
27 while True:
28 line = file.next()
29 if line == '' or line == '\n':
30 break
31 if line[0].isspace():
32 if h is None:
33 bad('unexpected continuation')
34 h += line
35 else:
36 if h: yield h
37 h = line
38 if h: yield h
39
40def hdrsplit(h):
41 v = h.split(':', 1)
42 if len(v) != 2:
43 bad('failed to parse header')
44 return v[0].strip().lower(), v[1].strip()
45
46remote = ('localhost', 119)
47approved = None
48try:
49 host = OS.popen('hostname -f').read().strip()
50except:
51 host = 'localhost'
52dist = 'mail'
53path = 'newsgate'
54group = None
55
56def opts():
57 global approved, remote, host, dist, path, group
58 try:
59 opts, args = getopt(argv[1:], 'a:d:h:r:p:',
60 ['approved=', 'distribution=',
61 'hostname=', 'remote=', 'path='])
62 except GetoptError:
63 usage()
64 for o, a in opts:
65 if o in ('-a', '--approved'):
66 approved = a
67 elif o in ('-d', '--distribution'):
68 dist = a
69 elif o in ('-h', '--hostname'):
70 host = a
71 elif o in ('-r', '--remote'):
72 remote = (lambda addr, port = 119: (addr, int(port)))(*a.split(':'))
73 if len(args) != 1:
74 usage()
75 group, = args
76
77rx_msgid = RX.compile(r'^\<\S+@\S+\>$')
78
79class NNTP (object):
80 def __init__(me, addr):
81 me.sk = S.socket(S.AF_INET, S.SOCK_STREAM)
82 me.sk.connect(remote)
83 me.f = me.sk.makefile()
84 rc, msg = me.reply()
85 if rc != '200':
86 die('unable to contact server: %s %s' % (rc, msg))
87 def write(me, stuff):
88 me.f.write(stuff)
89 def flush(me):
90 me.f.flush()
91 def cmd(me, stuff):
92 me.f.write(stuff + '\r\n')
93 me.f.flush()
94 def reply(me):
95 rc, msg = (lambda rc, msg = '.': (rc, msg.strip())) \
96 (*me.f.readline().split(None, 1))
97 if rc.startswith('5'):
98 die('server hated me: %s %s' % (rc, msg))
99 return rc, msg.strip()
100
101def send():
102 hdr = StringIO()
103 hdr.write('Path: newsgate\r\n'
104 'Distribution: mail\r\n'
105 'Newsgroups: %s\r\n'
106 'Approved: %s\r\n'
107 % (group, approved or 'newsgate@%s' % host))
108 xify = {}
109 for h in '''
110 lines xref newsgroups path distribution approved received
111 '''.split():
112 xify[h] = 1
113 seen = {}
114 for h in headers(stdin):
115 n, c = hdrsplit(h)
116 if n in xify:
117 h = 'X-Newsgate-' + h
118 elif h.startswith('.'):
119 h = '.' + h
120 seen[n] = c
121 if h.endswith('\r\n'):
122 pass
123 elif h.endswith('\n'):
124 h = h[:-1] + '\r\n'
125 else:
126 h += '\r\n'
127 hdr.write(h)
128 if 'message-id' not in seen:
129 seen['message-id'] = ('<newsgate-%s@%s>'
130 % (OS.popen('gorp 128').read().strip(),
131 host))
132 hdr.write('Message-ID: %s\r\n' % seen['message-id'])
133 if 'date' not in seen:
134 hdr.write('Date: %s\r\n'
135 % (T.strftime('%a, %d %b %Y %H:%M:%S %Z')))
136 if 'subject' not in seen:
137 hdr.write('Subject: (no subject)\r\n')
138 hdr.write('\r\n')
139
140 msgid = seen['message-id']
141 if not rx_msgid.match(msgid):
142 bad('invalid message-id %s' % msgid)
143
144 nntp = NNTP(remote)
145 nntp.cmd('IHAVE %s' % msgid)
146 rc, msg = nntp.reply()
147 if rc == '335':
148 nntp.write(hdr.getvalue())
149 for i in stdin:
150 if i.startswith('.'):
151 i = '.' + i
152 if i.endswith('\r\n'):
153 pass
154 elif i.endswith('\n'):
155 i = i[:-1] + '\r\n'
156 else:
157 i = i + '\r\n'
158 nntp.write(i)
159 nntp.write('.\r\n')
160 nntp.flush()
161 rc, msg = nntp.reply()
162 if rc == '435':
163 ## doesn't want my article; pretend all is fine: I don't care
164 pass
165 elif rc == '436':
166 die('failed to send article: %s %s' % (rc, msg))
167 elif rc == '437':
168 bad('server rejected article: %s %s' % (rc, msg))
169 elif not rc.startswith('2'):
170 die('unexpected response from server: %s %s' % (rc, msg))
171 nntp.cmd('QUIT')
172 nntp.reply()
173
174def main():
175 try:
176 opts()
177 send()
178 except SystemExit:
179 raise
180# except Exception, exc:
181# die('unhandled exception: %s, %s' % (exc.__class__.__name__,
182# exc.args))
183main()