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