Commit | Line | Data |
---|---|---|
4aef1ddb MW |
1 | #! /usr/bin/python |
2 | ||
3 | import sre as RX | |
4 | import os as OS | |
5 | import time as T | |
6 | import socket as S | |
7 | from getopt import getopt, GetoptError | |
8 | from sys import stdin, stdout, stderr, argv, exit | |
9 | from cStringIO import StringIO | |
6fe461cc | 10 | env = OS.environ |
4aef1ddb MW |
11 | |
12 | prog = argv[0] | |
13 | ||
14 | def bad(msg): | |
15 | print >>stderr, '%s (fatal): %s' % (prog, msg) | |
16 | exit(100) | |
17 | def die(msg): | |
18 | print >>stderr, '%s: %s' % (prog, msg) | |
19 | exit(111) | |
20 | def usage(): | |
21 | print >>stderr, \ | |
22 | ('Usage: %s [-d DIST] [-h HOST] [-r REMOTE] [-p PATH] GROUP <MESSAGE' % | |
23 | prog) | |
24 | exit(111) | |
25 | ||
26 | def 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 | ||
41 | def 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 | ||
47 | remote = ('localhost', 119) | |
48 | approved = None | |
49 | try: | |
50 | host = OS.popen('hostname -f').read().strip() | |
51 | except: | |
52 | host = 'localhost' | |
53 | dist = 'mail' | |
54 | path = 'newsgate' | |
6fe461cc MW |
55 | sender = env.get('SENDER') |
56 | recip = env.get('RECIPIENT') | |
4aef1ddb MW |
57 | group = None |
58 | ||
59 | def 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 | ||
85 | rx_msgid = RX.compile(r'^\<\S+@\S+\>$') | |
86 | ||
87 | class 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 | ||
109 | def 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 | ||
189 | def 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)) | |
198 | main() |