1
by
This commit was manufactured by cvs2svn to create branch |
1 |
#! @PYTHON@ |
2 |
#
|
|
6
by bwarsaw
main(): Treat addresses case-insensitively for grouping purposes. |
3 |
# Copyright (C) 1998-2003 by the Free Software Foundation, Inc. |
1
by
This commit was manufactured by cvs2svn to create branch |
4 |
#
|
5 |
# This program is free software; you can redistribute it and/or |
|
6 |
# modify it under the terms of the GNU General Public License |
|
7 |
# as published by the Free Software Foundation; either version 2 |
|
8 |
# of the License, or (at your option) any later version. |
|
6
by bwarsaw
main(): Treat addresses case-insensitively for grouping purposes. |
9 |
#
|
1
by
This commit was manufactured by cvs2svn to create branch |
10 |
# This program is distributed in the hope that it will be useful, |
11 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 |
# GNU General Public License for more details. |
|
6
by bwarsaw
main(): Treat addresses case-insensitively for grouping purposes. |
14 |
#
|
1
by
This commit was manufactured by cvs2svn to create branch |
15 |
# You should have received a copy of the GNU General Public License |
6
by bwarsaw
main(): Treat addresses case-insensitively for grouping purposes. |
16 |
# along with this program; if not, write to the Free Software |
749
by tkikuchi
FSF office has moved to 51 Franklin Street. |
17 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
1
by
This commit was manufactured by cvs2svn to create branch |
18 |
|
19 |
"""Send password reminders for all lists to all users.
|
|
20 |
||
21 |
This program scans all mailing lists and collects users and their passwords,
|
|
22 |
grouped by the list's host_name if mm_cfg.VIRTUAL_HOST_OVERVIEW is true. Then
|
|
23 |
one email message is sent to each unique user (per-virtual host) containing
|
|
24 |
the list passwords and options url for the user. The password reminder comes
|
|
25 |
from the mm_cfg.MAILMAN_SITE_LIST, which must exist.
|
|
26 |
||
27 |
Usage: %(PROGRAM)s [options]
|
|
28 |
||
29 |
Options:
|
|
30 |
-l listname
|
|
31 |
--listname=listname
|
|
32 |
Send password reminders for the named list only. If omitted,
|
|
33 |
reminders are sent for all lists. Multiple -l/--listname options are
|
|
34 |
allowed.
|
|
35 |
||
36 |
-h/--help
|
|
37 |
Print this message and exit.
|
|
38 |
"""
|
|
39 |
||
40 |
# This puppy should probably do lots of logging. |
|
41 |
import sys |
|
42 |
import os |
|
43 |
import errno |
|
44 |
import getopt |
|
81
by bwarsaw
Backporting from the HEAD -- bin and cron scripts |
45 |
from types import UnicodeType |
1
by
This commit was manufactured by cvs2svn to create branch |
46 |
|
47 |
import paths |
|
48 |
# mm_cfg must be imported before the other modules, due to the side-effect of |
|
49 |
# it hacking sys.paths to include site-packages. Without this, running this |
|
50 |
# script from cron with python -S will fail. |
|
51 |
from Mailman import mm_cfg |
|
52 |
from Mailman import MailList |
|
53 |
from Mailman import Errors |
|
54 |
from Mailman import Utils |
|
55 |
from Mailman import Message |
|
56 |
from Mailman import i18n |
|
57 |
from Mailman.Logging.Syslog import syslog |
|
58 |
||
59 |
# Work around known problems with some RedHat cron daemons |
|
60 |
import signal |
|
61 |
signal.signal(signal.SIGCHLD, signal.SIG_DFL) |
|
62 |
||
63 |
NL = '\n' |
|
64 |
PROGRAM = sys.argv[0] |
|
65 |
||
66 |
_ = i18n._ |
|
67 |
||
68 |
||
69 |
||
70 |
def usage(code, msg=''): |
|
71 |
if code: |
|
72 |
fd = sys.stderr |
|
73 |
else: |
|
74 |
fd = sys.stdout |
|
75 |
print >> fd, _(__doc__) |
|
76 |
if msg: |
|
77 |
print >> fd, msg |
|
78 |
sys.exit(code) |
|
79 |
||
80 |
||
81 |
||
81
by bwarsaw
Backporting from the HEAD -- bin and cron scripts |
82 |
def tounicode(s, enc): |
83 |
if isinstance(s, UnicodeType): |
|
84 |
return s |
|
85 |
return unicode(s, enc, 'replace') |
|
86 |
||
87 |
||
88 |
||
1
by
This commit was manufactured by cvs2svn to create branch |
89 |
def main(): |
90 |
try: |
|
91 |
opts, args = getopt.getopt(sys.argv[1:], 'l:h', |
|
92 |
['listname=', 'help']) |
|
93 |
except getopt.error, msg: |
|
94 |
usage(1, msg) |
|
95 |
||
96 |
if args: |
|
97 |
usage(1) |
|
98 |
||
99 |
listnames = None |
|
100 |
for opt, arg in opts: |
|
101 |
if opt in ('-h', '--help'): |
|
102 |
usage(0) |
|
103 |
if opt in ('-l', '--listname'): |
|
104 |
if listnames is None: |
|
105 |
listnames = [arg] |
|
106 |
else: |
|
107 |
listnames.append(arg) |
|
108 |
||
109 |
if listnames is None: |
|
110 |
listnames = Utils.list_names() |
|
111 |
||
112 |
# This is the list that all the reminders will look like they come from, |
|
113 |
# but with the host name coerced to the virtual host we're processing. |
|
114 |
try:
|
|
115 |
sitelist = MailList.MailList(mm_cfg.MAILMAN_SITE_LIST, lock=0)
|
|
116 |
except Errors.MMUnknownListError:
|
|
117 |
# Do it this way for I18n's _() |
|
118 |
sitelistname = mm_cfg.MAILMAN_SITE_LIST |
|
119 |
print >> sys.stderr, _('Site list is missing: %(sitelistname)s') |
|
120 |
syslog('error', 'Site list is missing: %s', mm_cfg.MAILMAN_SITE_LIST) |
|
121 |
sys.exit(1) |
|
122 |
||
123 |
# Group lists by host_name if VIRTUAL_HOST_OVERVIEW is true, otherwise |
|
124 |
# there's only one key in this dictionary: mm_cfg.DEFAULT_EMAIL_HOST. The |
|
125 |
# values are lists of the unlocked MailList instances.
|
|
126 |
byhost = {}
|
|
127 |
for listname in listnames:
|
|
128 |
mlist = MailList.MailList(listname, lock=0)
|
|
129 |
if not mlist.send_reminders:
|
|
130 |
continue
|
|
131 |
if mm_cfg.VIRTUAL_HOST_OVERVIEW:
|
|
132 |
host = mlist.host_name
|
|
133 |
else:
|
|
134 |
# See the note in Defaults.py concerning DEFAULT_HOST_NAME
|
|
135 |
# vs. DEFAULT_EMAIL_HOST.
|
|
136 |
host = mm_cfg.DEFAULT_HOST_NAME or mm_cfg.DEFAULT_EMAIL_HOST
|
|
137 |
byhost.setdefault(host, []).append(mlist)
|
|
138 |
||
139 |
# Now for each virtual host, collate the user information. Each user
|
|
140 |
# entry has the form (listaddr, password, optionsurl)
|
|
141 |
for host in byhost.keys():
|
|
142 |
# Site owner is `mailman@dom.ain'
|
|
143 |
userinfo = {} |
|
144 |
for mlist in byhost[host]: |
|
145 |
listaddr = mlist.GetListEmail() |
|
146 |
for member in mlist.getMembers(): |
|
20
by bwarsaw
Backporting from the trunk. |
147 |
# The user may have disabled reminders for this list |
148 |
if mlist.getMemberOption(member, |
|
149 |
mm_cfg.SuppressPasswordReminder): |
|
150 |
continue |
|
6
by bwarsaw
main(): Treat addresses case-insensitively for grouping purposes. |
151 |
# Group by the lower-cased address, since Mailman always |
152 |
# treates person@dom.ain the same as PERSON@dom.ain. |
|
81
by bwarsaw
Backporting from the HEAD -- bin and cron scripts |
153 |
try: |
154 |
password = mlist.getMemberPassword(member) |
|
155 |
except Errors.NotAMemberError: |
|
156 |
# Here's a member with no passwords, which I think was |
|
157 |
# possible in older versions of Mailman. Log this and
|
|
158 |
# move on.
|
|
159 |
syslog('error', 'password-less member %s for list %s', |
|
160 |
member, mlist.internal_name())
|
|
161 |
continue
|
|
1
by
This commit was manufactured by cvs2svn to create branch |
162 |
optionsurl = mlist.GetOptionsURL(member)
|
163 |
lang = mlist.getMemberLanguage(member)
|
|
164 |
info = (listaddr, password, optionsurl, lang)
|
|
6
by bwarsaw
main(): Treat addresses case-insensitively for grouping purposes. |
165 |
userinfo.setdefault(member, []).append(info)
|
1
by
This commit was manufactured by cvs2svn to create branch |
166 |
# Now that we've collected user information for this host, send each |
167 |
# user the password reminder. |
|
168 |
for addr in userinfo.keys(): |
|
169 |
# If the person is on more than one list, it is possible that they |
|
170 |
# have different preferred languages, and there's no good way to |
|
171 |
# know which one they want their password reminder in. Pick the
|
|
172 |
# most popular, and break the tie randomly.
|
|
173 |
#
|
|
174 |
# Also, we need an example -request address for cronpass.txt and
|
|
175 |
# again, there's no clear winner. Just take the first one in this |
|
176 |
# case. |
|
177 |
table = [] |
|
178 |
langs = {} |
|
179 |
for listaddr, password, optionsurl, lang in userinfo[addr]: |
|
180 |
langs[lang] = langs.get(lang, 0) + 1 |
|
181 |
# If the list address is really long, break it across two |
|
182 |
# lines. |
|
183 |
if len(listaddr) > 39: |
|
184 |
fmt = '%s\n %-10s\n%s\n' |
|
185 |
else: |
|
186 |
fmt = '%-40s %-10s\n%s\n' |
|
187 |
table.append(fmt % (listaddr, password, optionsurl)) |
|
188 |
# Figure out which language to use |
|
189 |
langcnt = 0 |
|
190 |
poplang = None |
|
191 |
for lang, cnt in langs.items(): |
|
192 |
if cnt > langcnt: |
|
193 |
poplang = lang |
|
194 |
langcnt = cnt |
|
81
by bwarsaw
Backporting from the HEAD -- bin and cron scripts |
195 |
enc = Utils.GetCharSet(poplang) |
1
by
This commit was manufactured by cvs2svn to create branch |
196 |
# Now we're finally ready to send the email! |
197 |
siteowner = Utils.get_site_email(host, 'owner') |
|
198 |
sitereq = Utils.get_site_email(host, 'request') |
|
199 |
sitebounce = Utils.get_site_email(host, 'bounces') |
|
200 |
text = Utils.maketext(
|
|
201 |
'cronpass.txt', |
|
202 |
{'hostname': host, |
|
203 |
'useraddr': addr, |
|
204 |
'exreq' : sitereq, |
|
205 |
'owner' : siteowner, |
|
206 |
}, lang=poplang)
|
|
81
by bwarsaw
Backporting from the HEAD -- bin and cron scripts |
207 |
# Coerce everything to Unicode
|
208 |
text = tounicode(text, enc)
|
|
209 |
table = [tounicode(_t, enc) for _t in table]
|
|
1
by
This commit was manufactured by cvs2svn to create branch |
210 |
# Translate the message and headers to user's suggested lang |
211 |
otrans = i18n.get_translation() |
|
212 |
try: |
|
213 |
i18n.set_language(poplang) |
|
503
by tkikuchi
i18n minor bug fix. |
214 |
# Craft table header after language was set |
215 |
header = '%-40s %-10s\n%-40s %-10s' % ( |
|
216 |
_('List'), _('Password // URL'), '----', '--------') |
|
217 |
header = tounicode(header, enc) |
|
218 |
# Add the table to the end so it doesn't get wrapped/filled |
|
219 |
text += (header + '\n' + NL.join(table)) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
220 |
msg = Message.UserNotification(
|
221 |
addr, siteowner,
|
|
222 |
_('%(host)s mailing list memberships reminder'), |
|
528
by tkikuchi
'replace' here again for alian charset password (should we restrict password |
223 |
text.encode(enc, 'replace'), poplang) |
503
by tkikuchi
i18n minor bug fix. |
224 |
# Note that text must be encoded into 'enc' because unicode |
225 |
# cause error within email module in some language (Japanese).
|
|
1
by
This commit was manufactured by cvs2svn to create branch |
226 |
finally:
|
227 |
i18n.set_translation(otrans)
|
|
228 |
msg['X-No-Archive'] = 'yes' |
|
229 |
# We want to make this look like it's coming from the siteowner's |
|
230 |
# list, but we also want to be sure that the apparent host name is
|
|
231 |
# the current virtual host. Look in CookHeaders.py for why this
|
|
232 |
# trick works. Blarg.
|
|
233 |
msg.send(sitelist, **{'errorsto': sitebounce, |
|
234 |
'_nolist' : 1, |
|
235 |
'verp' : mm_cfg.VERP_PASSWORD_REMINDERS, |
|
236 |
})
|
|
237 |
||
238 |
||
239 |
||
240 |
if __name__ == '__main__': |
|
241 |
main() |