~sambuddhabasu1/mailman/fix_mailman_run_error

« back to all changes in this revision

Viewing changes to src/mailman/runners/nntp.py

  • Committer: Barry Warsaw
  • Date: 2012-04-01 18:53:38 UTC
  • mfrom: (7139.1.4 bug-967409)
  • Revision ID: barry@list.org-20120401185338-5qujo0c3kc9a8wtr
 * The `news` runner and queue has been renamed to the more accurate `nntp`.
   The runner has also been ported to Mailman 3 (LP: #967409).  Beta testers
   can can safely remove `$var_dir/queue/news`.

 * Configuration schema variable changes:
   [nntp]username -> [nntp]user
   [nntp]port (added)

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
"""NNTP runner."""
19
19
 
 
20
from __future__ import absolute_import, print_function, unicode_literals
 
21
 
 
22
__metaclass__ = type
 
23
__all__ = [
 
24
    'NNTPRunner',
 
25
    ]
 
26
 
 
27
 
20
28
import re
21
29
import email
22
30
import socket
24
32
import nntplib
25
33
 
26
34
from cStringIO import StringIO
27
 
from lazr.config import as_host_port
28
35
 
29
36
from mailman.config import config
30
37
from mailman.core.runner import Runner
31
38
from mailman.interfaces.nntp import NewsModeration
32
39
 
 
40
COMMA = ','
33
41
COMMASPACE = ', '
34
42
log = logging.getLogger('mailman.error')
35
43
 
49
57
 
50
58
 
51
59
 
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()
 
65
        if len(port) == 0:
 
66
            port = 119
 
67
        else:
 
68
            try:
 
69
                port = int(port)
 
70
            except (TypeError, ValueError):
 
71
                log.exception('Bad [nntp]port value: {0}'.format(port))
 
72
                port = 119
54
73
        # Make sure we have the most up-to-date state
55
 
        mlist.Load()
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())
 
78
        conn = None
58
79
        try:
59
 
            # Flatten the message object, sticking it in a StringIO object
60
 
            fp = StringIO(msg.as_string())
61
 
            conn = None
62
 
            try:
63
 
                try:
64
 
                    nntp_host, nntp_port = as_host_port(
65
 
                        mlist.nntp_host, default_port=119)
66
 
                    conn = nntplib.NNTP(nntp_host, nntp_port,
67
 
                                        readermode=True,
68
 
                                        user=config.nntp.username,
69
 
                                        password=config.nntp.password)
70
 
                    conn.post(fp)
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)
77
 
            finally:
78
 
                if conn:
79
 
                    conn.quit()
80
 
        except Exception, e:
 
80
            conn = nntplib.NNTP(host, port,
 
81
                                readermode=True,
 
82
                                user=config.nntp.user,
 
83
                                password=config.nntp.password)
 
84
            conn.post(fp)
 
85
        except nntplib.error_temp:
 
86
            log.exception('{0} NNTP error for {1}'.format(
 
87
                msg.get('message-id', 'n/a'), mlist.fqdn_listname))
 
88
        except socket.error:
 
89
            log.exception('{0} NNTP socket error for {1}'.format(
 
90
                msg.get('message-id', 'n/a'), mlist.fqdn_listname))
 
91
        except Exception:
81
92
            # Some other exception occurred, which we definitely did not
82
93
            # expect, so set this message up for requeuing.
83
 
            self._log(e)
 
94
            log.exception('{0} NNTP unexpected exception for {1}'.format(
 
95
                msg.get('message-id', 'n/a'), mlist.fqdn_listname))
84
96
            return True
 
97
        finally:
 
98
            if conn:
 
99
                conn.quit()
85
100
        return False
86
101
 
87
102
 
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')
 
127
    if header is None:
 
128
        msg['Newsgroups'] = mlist.linked_newsgroup
 
129
    else:
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)
119
 
    else:
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
128
 
    # looping.
 
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.
129
145
    #
130
146
    # Our Message-ID format is <mailman.secs.pid.listname@hostname>
 
147
    #
 
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']
132
153
    hackmsgid = True
133
154
    if msgid:
139
160
    if hackmsgid:
140
161
        del msg['message-id']
141
162
        msg['Message-ID'] = email.utils.make_msgid()
142
 
    # Lines: is useful
 
163
    # Lines: is useful.
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():
152
173
        del msg[header]
153
 
    for rewrite_pairs in config.nntp.rewrite_duplicate_headers.splitlines():
154
 
        if len(rewrite_pairs.strip()) == 0:
155
 
            continue
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(
 
179
            bad_header))
 
180
    dup_headers.reverse()
 
181
    while dup_headers:
 
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.
160
187
            continue
161
 
        del msg[header]
162
 
        # But keep the first one...
163
 
        msg[header] = values[0]
164
 
        for v in values[1:]:
165
 
            msg[rewrite] = v
166
 
    # Mark this message as prepared in case it has to be requeued
 
188
        # Delete all the original headers.
 
189
        del msg[source]
 
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:]:
 
194
            msg[target] = value
 
195
    # Mark this message as prepared in case it has to be requeued.
167
196
    msgdata['prepped'] = True