52
class NewsRunner(Runner):
60
class NNTPRunner(Runner):
53
61
def _dispose(self, mlist, msg, msgdata):
62
# Get NNTP server connection information.
63
host = config.nntp.host.strip()
64
port = config.nntp.port.strip()
70
except (TypeError, ValueError):
71
log.exception('Bad [nntp]port value: {0}'.format(port))
54
73
# Make sure we have the most up-to-date state
56
74
if not msgdata.get('prepped'):
57
75
prepare_message(mlist, msg, msgdata)
76
# Flatten the message object, sticking it in a StringIO object
77
fp = StringIO(msg.as_string())
59
# Flatten the message object, sticking it in a StringIO object
60
fp = StringIO(msg.as_string())
64
nntp_host, nntp_port = as_host_port(
65
mlist.nntp_host, default_port=119)
66
conn = nntplib.NNTP(nntp_host, nntp_port,
68
user=config.nntp.username,
69
password=config.nntp.password)
71
except nntplib.error_temp, e:
72
log.error('(NNTPDirect) NNTP error for list "%s": %s',
73
mlist.internal_name(), e)
74
except socket.error, e:
75
log.error('(NNTPDirect) socket error for list "%s": %s',
76
mlist.internal_name(), e)
80
conn = nntplib.NNTP(host, port,
82
user=config.nntp.user,
83
password=config.nntp.password)
85
except nntplib.error_temp:
86
log.exception('{0} NNTP error for {1}'.format(
87
msg.get('message-id', 'n/a'), mlist.fqdn_listname))
89
log.exception('{0} NNTP socket error for {1}'.format(
90
msg.get('message-id', 'n/a'), mlist.fqdn_listname))
81
92
# Some other exception occurred, which we definitely did not
82
93
# expect, so set this message up for requeuing.
94
log.exception('{0} NNTP unexpected exception for {1}'.format(
95
msg.get('message-id', 'n/a'), mlist.fqdn_listname))
99
114
# messages? TK: We use stripped_subject (prefix stripped) which was
100
115
# crafted in CookHeaders.py to ensure prefix was stripped from the subject
101
116
# came from mailing list user.
102
stripped_subject = msgdata.get('stripped_subject') \
103
or msgdata.get('origsubj')
117
stripped_subject = msgdata.get('stripped_subject',
118
msgdata.get('original_subject'))
119
# XXX 2012-03-31 BAW: rename news_prefix_subject_too to nntp_. This
120
# requires a schema change.
104
121
if not mlist.news_prefix_subject_too and stripped_subject is not None:
105
122
del msg['subject']
106
123
msg['subject'] = stripped_subject
107
# Add the appropriate Newsgroups: header
108
ngheader = msg['newsgroups']
109
if ngheader is not None:
124
# Add the appropriate Newsgroups header. Multiple Newsgroups headers are
125
# generally not allowed so we're not testing for them.
126
header = msg.get('newsgroups')
128
msg['Newsgroups'] = mlist.linked_newsgroup
110
130
# See if the Newsgroups: header already contains our linked_newsgroup.
111
131
# If so, don't add it again. If not, append our linked_newsgroup to
112
132
# the end of the header list
113
ngroups = [s.strip() for s in ngheader.split(',')]
114
if mlist.linked_newsgroup not in ngroups:
115
ngroups.append(mlist.linked_newsgroup)
133
newsgroups = [value.strip() for value in header.split(COMMA)]
134
if mlist.linked_newsgroup not in newsgroups:
135
newsgroups.append(mlist.linked_newsgroup)
116
136
# Subtitute our new header for the old one.
117
137
del msg['newsgroups']
118
msg['Newsgroups'] = COMMASPACE.join(ngroups)
120
# Newsgroups: isn't in the message
121
msg['Newsgroups'] = mlist.linked_newsgroup
138
msg['Newsgroups'] = COMMASPACE.join(newsgroups)
122
139
# Note: We need to be sure two messages aren't ever sent to the same list
123
140
# in the same process, since message ids need to be unique. Further, if
124
# messages are crossposted to two Usenet-gated mailing lists, they each
125
# need to have unique message ids or the nntpd will only accept one of
126
# them. The solution here is to substitute any existing message-id that
127
# isn't ours with one of ours, so we need to parse it to be sure we're not
141
# messages are crossposted to two gated mailing lists, they must each have
142
# unique message ids or the nntpd will only accept one of them. The
143
# solution here is to substitute any existing message-id that isn't ours
144
# with one of ours, so we need to parse it to be sure we're not looping.
130
146
# Our Message-ID format is <mailman.secs.pid.listname@hostname>
148
# XXX 2012-03-31 BAW: What we really want to do is try posting the message
149
# to the nntpd first, and only if that fails substitute a unique
150
# Message-ID. The following should get moved out of prepare_message() and
151
# into _dispose() above.
131
152
msgid = msg['message-id']
140
161
del msg['message-id']
141
162
msg['Message-ID'] = email.utils.make_msgid()
143
164
if msg['Lines'] is None:
144
165
# BAW: is there a better way?
145
count = len(list(email.Iterators.body_line_iterator(msg)))
166
count = len(list(email.iterators.body_line_iterator(msg)))
146
167
msg['Lines'] = str(count)
147
168
# Massage the message headers by remove some and rewriting others. This
148
# woon't completely sanitize the message, but it will eliminate the bulk
149
# of the rejections based on message headers. The NNTP server may still
169
# won't completely sanitize the message, but it will eliminate the bulk of
170
# the rejections based on message headers. The NNTP server may still
150
171
# reject the message because of other problems.
151
172
for header in config.nntp.remove_headers.split():
153
for rewrite_pairs in config.nntp.rewrite_duplicate_headers.splitlines():
154
if len(rewrite_pairs.strip()) == 0:
156
header, rewrite = rewrite_pairs.split()
157
values = msg.get_all(header, [])
174
dup_headers = config.nntp.rewrite_duplicate_headers.split()
175
if len(dup_headers) % 2 != 0:
176
# There are an odd number of headers; ignore the last one.
177
bad_header = dup_headers.pop()
178
log.error('Ignoring odd [nntp]rewrite_duplicate_headers: {0}'.format(
180
dup_headers.reverse()
182
source = dup_headers.pop()
183
target = dup_headers.pop()
184
values = msg.get_all(source, [])
158
185
if len(values) < 2:
159
# We only care about duplicates
186
# We only care about duplicates.
162
# But keep the first one...
163
msg[header] = values[0]
166
# Mark this message as prepared in case it has to be requeued
188
# Delete all the original headers.
190
# Put the first value back on the original header.
191
msg[source] = values[0]
192
# And put all the subsequent values on the destination header.
193
for value in values[1:]:
195
# Mark this message as prepared in case it has to be requeued.
167
196
msgdata['prepped'] = True