summaryrefslogtreecommitdiff
path: root/scripts/driver
blob: 0fd025017fdeb8da60a24ba83eb3a412090382fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# -*- python -*-

# Copyright (C) 1998-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.

# This better succeed.  If this fails, Python is royally screwed so we might
# as well let the Web server give us a fatal and obtrusive error.
import sys

# From here on we are as bulletproof as possible!

# The driver script prints out a lot of information when a Mailman bug is
# encountered.  This really helps for development, but it also reveals
# information about the host system that some administrators are not
# comfortable with.  By setting STEALTH_MODE to True, you disable the printing
# of this information to the web pages.  This information is still, and
# always, printed in the error logs.
STEALTH_MODE = False

# This will be set to the entity escaper.
def websafe(s):
    return s

SPACE = ' '



# This standard driver script is used to run CGI programs, wrapped in code
# that catches errors, and displays them as HTML.  This guarantees that
# (almost) any problem in the Mailman software doesn't result in a Web server
# error.  It is much more helpful to generate and show a traceback, which the
# user could send to the administrator, than to display a server error and
# have to trudge through server logs.

# Note: this isn't 100% perfect!  Here are some things that can go wrong that
# are not caught and reported as traceback-containing HTML:
#
# - This file could contain a syntax error.  In that case, you would indeed
#   get a Web server error since this file wouldn't even compile, and there's
#   no way to catch that.  Mailman's install procedure should make this highly
#   unlikely.
#
# - The sys module could be royally screwed, probably we couldn't import it.
#   This would indicate a serious problem with the Python installation, so
#   it's also highly unlikely to occur.


def run_main():
    global STEALTH_MODE, websafe

    # These will ensure that even if something between now and the
    # creation of the real logger below fails, we can still get
    # *something* meaningful.
    log = None
    try:
        import paths
        from Mailman.configuration import config
        config.load()
        # When running in non-stealth mode, we need to escape entities,
        # otherwise we're vulnerable to cross-site scripting attacks.
        try:
            if not STEALTH_MODE:
                from Mailman.Utils import websafe
        except:
            STEALTH_MODE = True
            raise
        # Initialize the standard loggers
        from Mailman.loginit import initialize
        initialize()
        import logging
        log = logging.getLogger('mailman.error')
        # Collect stdout and stderr in cStringIOs so that if /any/ errors
        # occur during printing it won't mess up our diagnostics page.
        from cStringIO import StringIO
        tempstdout = StringIO()
        tempstderr = StringIO()
        # The name of the module to run is passed in argv[1].  What we
        # actually do is import the module named by argv[1] that lives in the
        # Mailman.Cgi package.  That module must have a main() function, which
        # we dig out and call.
        scriptname = sys.argv[1]
        # See the reference manual for why we have to do things this way.
        # Note that importing should have no side-effects!
        pkg = __import__('Mailman.Cgi', globals(), locals(), [scriptname])
        module = getattr(pkg, scriptname)
        main = getattr(module, 'main')
        import signal
        signal.signal(signal.SIGTERM, sigterm_handler)
        try:
            try:
                sys.stdout = tempstdout
                sys.stderr = tempstderr
                main()
                sys.__stdout__.write(tempstdout.getvalue())
                sys.__stderr__.write(tempstderr.getvalue())
            finally:
                sys.stderr = sys.__stderr__
                sys.stdout = sys.__stdout__
        except SystemExit:
            # This is a valid way for the function to exit.  Be sure any text
            # produced is still written out to the browser.
            sys.stdout.write(tempstdout.getvalue())
    except:
        print_traceback(log)
        print_environment(log)



# If possible, we print the error to two places.  One will always be stdout
# and the other will be the log file if a log file was created.  It is assumed
# that stdout is an HTML sink.
def print_traceback(log=None):
    try:
        import traceback
    except ImportError:
        traceback = None
    try:
        from Mailman.mm_cfg import VERSION
    except ImportError:
        VERSION = '<undetermined>'

    # Write to the log file first.
    if log:
        from cStringIO import StringIO
        outfp = StringIO()

        print >> outfp, '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@'
        print >> outfp, '[----- Mailman Version: %s -----]' % VERSION
        print >> outfp, '[----- Traceback ------]'
        if traceback:
            traceback.print_exc(file=outfp)
        else:
            print >> outfp, '[failed to import module traceback]'
            print >> outfp, '[exc: %s, var: %s]' % sys.exc_info()[0:2]
        # Don't use .exception() since that'll give us the exception twice.
        # IWBNI we could print directly to the log's stream, or treat a log
        # like an output stream.
        log.error('%s', outfp.getvalue())

    # Write to the HTML sink.
    print """\
Content-type: text/html

<head><title>Bug in Mailman version %(VERSION)s</title></head>
<body bgcolor=#ffffff><h2>Bug in Mailman version %(VERSION)s</h2>
<p><h3>We're sorry, we hit a bug!</h3>
""" % locals()
    if not STEALTH_MODE:
        print '''<p>If you would like to help us identify the problem,
please email a copy of this page to the webmaster for this site with
a description of what happened.  Thanks!

<h4>Traceback:</h4><p><pre>'''
        exc_info = sys.exc_info()
        if traceback:
            for line in traceback.format_exception(*exc_info):
                print websafe(line),
        else:
            print '[failed to import module traceback]'
            print '[exc: %s, var: %s]' % [websafe(x) for x in exc_info[0:2]]
        print '\n\n</pre></body>'
    else:
        print '''<p>Please inform the webmaster for this site of this
problem.  Printing of traceback and other system information has been
explicitly inhibited, but the webmaster can find this information in the
Mailman error logs.'''



def print_environment(log=None):
    try:
        import os
    except ImportError:
        os = None

    if log:
        from cStringIO import StringIO
        outfp = StringIO()

        # Write some information about our Python executable to the log file.
        print >> outfp, '[----- Python Information -----]'
        print >> outfp, 'sys.version     =', sys.version
        print >> outfp, 'sys.executable  =', sys.executable
        print >> outfp, 'sys.prefix      =', sys.prefix
        print >> outfp, 'sys.exec_prefix =', sys.exec_prefix
        print >> outfp, 'sys.path        =', sys.exec_prefix
        print >> outfp, 'sys.platform    =', sys.platform
        print >> outfp, 'args            =', SPACE.join(sys.argv)

    # Write the same information to the HTML sink.
    if not STEALTH_MODE:
        print '''\
<p><hr><h4>Python information:</h4>

<p><table>
<tr><th>Variable</th><th>Value</th></tr>
'''
        print '<tr><td><tt>sys.version</tt></td><td>', \
              sys.version, '</td></tr>'
        print '<tr><td><tt>sys.executable</tt></td><td>', \
              sys.executable, '</td></tr>'
        print '<tr><td><tt>sys.prefix</tt></td><td>', sys.prefix, '</td></tr>'
        print '<tr><td><tt>sys.exec_prefix</tt></td><td>', \
              sys.exec_prefix, '</td></tr>'
        # what else?
        print '<tr><td><tt>sys.path</tt></td><td>', \
              sys.exec_prefix, '</td></tr>'
        print '<tr><td><tt>sys.platform</tt></td><td>', \
              sys.platform, '</td></tr>'
        print '</table>'

    # Write environment variables to the log file.
    if log:
        print >> logfp, '[----- Environment Variables -----]'
        if os:
            for k, v in os.environ.items():
                print >> outfp, '\t%s: %s' % (k, v)
        else:
            print >> outfp, '[failed to import module os]'

    # Write environment variables to the HTML sink.
    if not STEALTH_MODE:
        print '''\
<p><hr><h4>Environment variables:</h4>

<p><table>
<tr><th>Variable</th><th>Value</th></tr>
'''
        if os:
            for k, v in os.environ.items():
                print '<tr><td><tt>', websafe(k), \
                      '</tt></td><td>', websafe(v), \
                      '</td></tr>'
            print '</table>'
        else:
            print '<p><hr>[failed to import module os]'

    # Dump the log output
    if log:
        log.error('%s', outfp.getvalue())



try:
    run_main()
except:
    # Some exception percolated all the way back up to the top.  This
    # generally shouldn't happen because the run_main() call is similarly
    # wrapped, but just in case, we'll give it one last ditch effort to report
    # problems to *somebody*.  Most likely this will end up in the Web server
    # log file.
    try:
        print_traceback()
        print_environment()
    except:
        # Nope, we're quite screwed
        print """\
Content-type: text/html

<p><h3>We're sorry, we hit a bug!</h3>

Mailman experienced a very low level failure and could not even generate a
useful traceback for you.  Please report this to the Mailman administrator at
this site.
"""
        print >> sys.__stderr__, '[Mailman: low level unrecoverable exception]'



# Signal handler to guarantee that, when running under traditional CGI, a
# locked mailing list will be unlocked when a fatal signal is received.
#
# try/finally isn't enough because of this scenario: user hits a CGI page
# which may take a long time to render; user gets bored and hits the browser's
# STOP button; browser shuts down socket; server tries to write to broken
# socket and gets a SIGPIPE.  Under Apache 1.3/mod_cgi, Apache catches this
# SIGPIPE (I presume it is buffering output from the cgi script), then turns
# around and SIGTERMs the cgi process.  Apache waits three seconds and then
# SIGKILLs the cgi process.  We /must/ catch the SIGTERM and do the most
# reasonable thing we can in as short a time period as possible.  If we get
# the SIGKILL we're screwed because it's uncatchable and we'll have no
# opportunity to clean up after ourselves.
#
# This signal handler catches the SIGTERM, unlocks the list, and then
# exits the process.  The effect of this is that the changes made to the
# MailList object will be aborted, which seems like the only sensible
# semantics.
#
# Note that we must not install this signal handler when running under the
# HTTPRunner wsgi server because the sys.exit(0) will cause the supervisor
# mailmanctl process to restart the HTTPRunner.  When running in that
# environment, just let the normal signal handling do the right thing.
def sigterm_handler(signum, frame, mlist=mlist):
    # Make sure the list gets unlocked...
    if mlist.Locked():
        mlist.Unlock()
    # ...and ensure we exit, otherwise race conditions could cause us to
    # enter MailList.Save() while we're in the unlocked state, and that
    # could be bad!
    sys.exit(0)