diff options
author | <> | 2003-01-02 05:25:50 +0000 |
---|---|---|
committer | <> | 2003-01-02 05:25:50 +0000 |
commit | b132a73f15e432eaf43310fce9196ca0c0651465 (patch) | |
tree | c15f816ba7c4de99fef510e3bd75af0890d47441 /bin/sync_members | |
download | mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.gz mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.xz mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.zip |
This commit was manufactured by cvs2svn to create branch
'Release_2_1-maint'.
Diffstat (limited to 'bin/sync_members')
-rwxr-xr-x | bin/sync_members | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/bin/sync_members b/bin/sync_members new file mode 100755 index 00000000..1bf9a454 --- /dev/null +++ b/bin/sync_members @@ -0,0 +1,286 @@ +#! @PYTHON@ +# +# Copyright (C) 1998,1999,2000,2001,2002 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""Synchronize a mailing list's membership with a flat file. + +This script is useful if you have a Mailman mailing list and a sendmail +:include: style list of addresses (also as is used in Majordomo). For every +address in the file that does not appear in the mailing list, the address is +added. For every address in the mailing list that does not appear in the +file, the address is removed. Other options control what happens when an +address is added or removed. + +Usage: %(PROGRAM)s [options] -f file listname + +Where `options' are: + + --no-change + -n + Don't actually make the changes. Instead, print out what would be + done to the list. + + --welcome-msg[=<yes|no>] + -w[=<yes|no>] + Sets whether or not to send the newly added members a welcome + message, overriding whatever the list's `send_welcome_msg' setting + is. With -w=yes or -w, the welcome message is sent. With -w=no, no + message is sent. + + --goodbye-msg[=<yes|no>] + -g[=<yes|no>] + Sets whether or not to send the goodbye message to removed members, + overriding whatever the list's `send_goodbye_msg' setting is. With + -g=yes or -g, the goodbye message is sent. With -g=no, no message is + sent. + + --digest[=<yes|no>] + -d[=<yes|no>] + Selects whether to make newly added members receive messages in + digests. With -d=yes or -d, they become digest members. With -d=no + (or if no -d option given) they are added as regular members. + + --notifyadmin[=<yes|no>] + -a[=<yes|no>] + Specifies whether the admin should be notified for each subscription + or unsubscription. If you're adding a lot of addresses, you + definitely want to turn this off! With -a=yes or -a, the admin is + notified. With -a=no, the admin is not notified. With no -a option, + the default for the list is used. + + --file <filename | -> + -f <filename | -> + This option is required. It specifies the flat file to synchronize + against. Email addresses must appear one per line. If filename is + `-' then stdin is used. + + --help + -h + Print this message. + + listname + Required. This specifies the list to synchronize. +""" + +import sys + +import paths +# Import this /after/ paths so that the sys.path is properly hacked +import email.Utils + +from Mailman import MailList +from Mailman import Errors +from Mailman import Utils +from Mailman.UserDesc import UserDesc +from Mailman.i18n import _ + + + +PROGRAM = sys.argv[0] + +def usage(code, msg=''): + if code: + fd = sys.stderr + else: + fd = sys.stdout + print >> fd, _(__doc__) + if msg: + print >> fd, msg + sys.exit(code) + + + +def yesno(opt): + i = opt.find('=') + yesno = opt[i+1:].lower() + if yesno in ('y', 'yes'): + return 1 + elif yesno in ('n', 'no'): + return 0 + else: + usage(1, _('Bad choice: %(yesno)s')) + # no return + + +def main(): + dryrun = 0 + digest = 0 + welcome = None + goodbye = None + filename = None + listname = None + notifyadmin = None + + # TBD: can't use getopt with this command line syntax, which is broken and + # should be changed to be getopt compatible. + i = 1 + while i < len(sys.argv): + opt = sys.argv[i] + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-n', '--no-change'): + dryrun = 1 + i += 1 + print _('Dry run mode') + elif opt in ('-d', '--digest'): + digest = 1 + i += 1 + elif opt.startswith('-d=') or opt.startswith('--digest='): + digest = yesno(opt) + i += 1 + elif opt in ('-w', '--welcome-msg'): + welcome = 1 + i += 1 + elif opt.startswith('-w=') or opt.startswith('--welcome-msg='): + welcome = yesno(opt) + i += 1 + elif opt in ('-g', '--goodbye-msg'): + goodbye = 1 + i += 1 + elif opt.startswith('-g=') or opt.startswith('--goodbye-msg='): + goodbye = yesno(opt) + i += 1 + elif opt in ('-f', '--file'): + if filename is not None: + usage(1, _('Only one -f switch allowed')) + try: + filename = sys.argv[i+1] + except IndexError: + usage(1, _('No argument to -f given')) + i += 2 + elif opt in ('-a', '--notifyadmin'): + notifyadmin = 1 + i += 1 + elif opt.startswith('-a=') or opt.startswith('--notifyadmin='): + notifyadmin = yesno(opt) + i += 1 + elif opt[0] == '-': + usage(1, _('Illegal option: %(opt)s')) + else: + try: + listname = sys.argv[i].lower() + i += 1 + except IndexError: + usage(1, _('No listname given')) + break + + if listname is None or filename is None: + usage(1, _('Must have a listname and a filename')) + + # read the list of addresses to sync to from the file + if filename == '-': + filemembers = sys.stdin.readlines() + else: + try: + fp = open(filename) + except IOError, (code, msg): + usage(1, _('Cannot read address file: %(filename)s: %(msg)s')) + try: + filemembers = fp.readlines() + finally: + fp.close() + + # strip out lines we don't care about, they are comments (# in first + # non-whitespace) or are blank + for i in range(len(filemembers)-1, -1, -1): + addr = filemembers[i].strip() + if addr == '' or addr[:1] == '#': + del filemembers[i] + print _('Ignore : %(addr)30s') + + # first filter out any invalid addresses + filemembers = email.Utils.getaddresses(filemembers) + invalid = 0 + for name, addr in filemembers: + try: + Utils.ValidateEmail(addr) + except Errors.EmailAddressError: + print _('Invalid : %(addr)30s') + invalid = 1 + if invalid: + print _('You must fix the preceding invalid addresses first.') + sys.exit(1) + + # get the locked list object + try: + mlist = MailList.MailList(listname) + except Errors.MMListError, e: + print _('No such list: %(listname)s') + sys.exit(1) + + try: + # Get the list of addresses currently subscribed + addrs = {} + needsadding = {} + matches = {} + for addr in mlist.getMemberCPAddresses(mlist.getMembers()): + addrs[addr.lower()] = addr + + for name, addr in filemembers: + # Any address found in the file that is also in the list can be + # ignored. If not found in the list, it must be added later. + laddr = addr.lower() + if addrs.has_key(laddr): + del addrs[laddr] + matches[laddr] = 1 + elif not matches.has_key(laddr): + needsadding[laddr] = (name, addr) + + if not needsadding and not addrs: + print _('Nothing to do.') + sys.exit(0) + + # addrs contains now all the addresses that need removing + for laddr, (name, addr) in needsadding.items(): + pw = Utils.MakeRandomPassword() + # should not already be subscribed, otherwise our test above is + # broken. Bogosity is if the address is listed in the file more + # than once. Second and subsequent ones trigger an + # MMAlreadyAMember error. Just catch it and go on. + userdesc = UserDesc(addr, name, pw, digest) + try: + if not dryrun: + mlist.ApprovedAddMember(userdesc, welcome, notifyadmin) + s = email.Utils.formataddr((name, addr)).encode(enc, 'replace') + print _('Added : %(s)s') + except Errors.MMAlreadyAMember: + pass + + for laddr, addr in addrs.items(): + # Should be a member, otherwise our test above is broken + if not dryrun: + try: + mlist.ApprovedDeleteMember(addr, admin_notif=notifyadmin, + userack=goodbye) + except Errors.NotAMemberError: + # This can happen if the address is illegal (i.e. can't be + # parsed by email.Utils.parseaddr()) but for legacy + # reasons is in the database. Use a lower level remove to + # get rid of this member's entry + mlist.removeMember(addr) + name = mlist.getMemberName(laddr) or '' + enc = sys.getdefaultencoding() + s = email.Utils.formataddr((name, addr)).encode(enc, 'replace') + print _('Removed: %(s)s') + + mlist.Save() + finally: + mlist.Unlock() + + +if __name__ == '__main__': + main() |