summaryrefslogtreecommitdiff
path: root/mailman/bin/mailmanctl.py
blob: 667a46a70c3614a3f8c5d46838cfa14cabcfe432 (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
# Copyright (C) 2001-2009 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
#
# GNU Mailman 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 3 of the License, or (at your option)
# any later version.
#
# GNU Mailman 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
# GNU Mailman.  If not, see <http://www.gnu.org/licenses/>.

"""Mailman start/stop script."""

import os
import grp
import pwd
import sys
import errno
import signal
import logging

from optparse import OptionParser

from mailman.config import config
from mailman.core.initialize import initialize
from mailman.i18n import _
from mailman.version import MAILMAN_VERSION


COMMASPACE = ', '

log = None
parser = None



def parseargs():
    parser = OptionParser(version=MAILMAN_VERSION,
                          usage=_("""\
Primary start-up and shutdown script for Mailman's qrunner daemon.

This script starts, stops, and restarts the main Mailman queue runners, making
sure that the various long-running qrunners are still alive and kicking.  It
does this by forking and exec'ing the qrunners and waiting on their pids.
When it detects a subprocess has exited, it may restart it.

The qrunners respond to SIGINT, SIGTERM, SIGUSR1 and SIGHUP.  SIGINT, SIGTERM
and SIGUSR1 all cause the qrunners to exit cleanly, but the master will only
restart qrunners that have exited due to a SIGUSR1.  SIGHUP causes the master
and the qrunners to close their log files, and reopen then upon the next
printed message.

The master also responds to SIGINT, SIGTERM, SIGUSR1 and SIGHUP, which it
simply passes on to the qrunners (note that the master will close and reopen
its own log files on receipt of a SIGHUP).  The master also leaves its own
process id in the file data/master-qrunner.pid but you normally don't need to
use this pid directly.  The `start', `stop', `restart', and `reopen' commands
handle everything for you.

Commands:

    start   - Start the master daemon and all qrunners.  Prints a message and
              exits if the master daemon is already running.

    stop    - Stops the master daemon and all qrunners.  After stopping, no
              more messages will be processed.

    restart - Restarts the qrunners, but not the master process.  Use this
              whenever you upgrade or update Mailman so that the qrunners will
              use the newly installed code.

    reopen  - This will close all log files, causing them to be re-opened the
              next time a message is written to them

Usage: %prog [options] [ start | stop | restart | reopen ]"""))
    parser.add_option('-u', '--run-as-user',
                      default=True, action='store_false',
                      help=_("""\
Normally, this script will refuse to run if the user id and group id are not
set to the `mailman' user and group (as defined when you configured Mailman).
If run as root, this script will change to this user and group before the
check is made.

This can be inconvenient for testing and debugging purposes, so the -u flag
means that the step that sets and checks the uid/gid is skipped, and the
program is run as the current user and group.  This flag is not recommended
for normal production environments.

Note though, that if you run with -u and are not in the mailman group, you may
have permission problems, such as begin unable to delete a list's archives
through the web.  Tough luck!"""))
    parser.add_option('-f', '--force',
                      default=False, action='store_true',
                      help=_("""\
If the master watcher finds an existing master lock, it will normally exit
with an error message.  With this option,the master will perform an extra
level of checking.  If a process matching the host/pid described in the lock
file is running, the master will still exit, requiring you to manually clean
up the lock.  But if no matching process is found, the master will remove the
apparently stale lock and make another attempt to claim the master lock."""))
    parser.add_option('-q', '--quiet',
                      default=False, action='store_true',
                      help=_("""\
Don't print status messages.  Error messages are still printed to standard
error."""))
    parser.add_option('-C', '--config',
                      help=_('Alternative configuration file to use'))
    options, arguments = parser.parse_args()
    if not arguments:
        parser.error(_('No command given.'))
    if len(arguments) > 1:
        commands = COMMASPACE.join(arguments)
        parser.error(_('Bad command: $commands'))
    parser.options = options
    parser.arguments = arguments
    return parser



def kill_watcher(sig):
    try:
        with open(config.PIDFILE) as f:
            pid = int(f.read().strip())
    except (IOError, ValueError), e:
        # For i18n convenience
        print >> sys.stderr, _('PID unreadable in: $config.PIDFILE')
        print >> sys.stderr, e
        print >> sys.stderr, _('Is qrunner even running?')
        return
    try:
        os.kill(pid, sig)
    except OSError, error:
        if e.errno <> errno.ESRCH:
            raise
        print >> sys.stderr, _('No child with pid: $pid')
        print >> sys.stderr, e
        print >> sys.stderr, _('Stale pid file removed.')
        os.unlink(config.PIDFILE)



def check_privileges():
    # If we're running as root (uid == 0), coerce the uid and gid to that
    # which Mailman was configured for, and refuse to run if we didn't coerce
    # the uid/gid.
    gid = grp.getgrnam(config.MAILMAN_GROUP).gr_gid
    uid = pwd.getpwnam(config.MAILMAN_USER).pw_uid
    myuid = os.getuid()
    if myuid == 0:
        # Set the process's supplimental groups.
        groups = [group.gr_gid for group in grp.getgrall()
                  if config.MAILMAN_USER in group.gr_mem]
        groups.append(gid)
        os.setgroups(groups)
        os.setgid(gid)
        os.setuid(uid)
    elif myuid <> uid:
        name = config.MAILMAN_USER
        parser.error(
            _('Run this program as root or as the $name user, or use -u.'))



def main():
    global log, parser

    parser = parseargs()
    initialize(parser.options.config)

    log = logging.getLogger('mailman.qrunner')

    if not parser.options.run_as_user:
        check_privileges()
    else:
        if not parser.options.quiet:
            print _('Warning!  You may encounter permission problems.')

    # Handle the commands
    command = parser.arguments[0].lower()
    if command == 'stop':
        if not parser.options.quiet:
            print _("Shutting down Mailman's master qrunner")
        kill_watcher(signal.SIGTERM)
    elif command == 'restart':
        if not parser.options.quiet:
            print _("Restarting Mailman's master qrunner")
        kill_watcher(signal.SIGUSR1)
    elif command == 'reopen':
        if not parser.options.quiet:
            print _('Re-opening all log files')
        kill_watcher(signal.SIGHUP)
    elif command == 'start':
        # Start the master qrunner watcher process.
        #
        # Daemon process startup according to Stevens, Advanced Programming in
        # the UNIX Environment, Chapter 13.
        pid = os.fork()
        if pid:
            # parent
            if not parser.options.quiet:
                print _("Starting Mailman's master qrunner.")
            return
        # child
        #
        # Create a new session and become the session leader, but since we
        # won't be opening any terminal devices, don't do the ultra-paranoid
        # suggestion of doing a second fork after the setsid() call.
        os.setsid()
        # Instead of cd'ing to root, cd to the Mailman runtime directory.
        os.chdir(config.VAR_DIR)
        # Exec the master watcher.
        args = [sys.executable, sys.executable,
                os.path.join(config.BIN_DIR, 'master')]
        if parser.options.force:
            args.append('--force')
        if parser.options.config:
            args.extend(['-C', parser.options.config])
        log.debug('starting: %s', args)
        os.execl(*args)
        # We should never get here.
        raise RuntimeError('os.execl() failed')



if __name__ == '__main__':
    main()