aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--bin/export.py71
1 files changed, 61 insertions, 10 deletions
diff --git a/bin/export.py b/bin/export.py
index c2592aa9..37797cd9 100644
--- a/bin/export.py
+++ b/bin/export.py
@@ -19,7 +19,10 @@
"""Export an XML representation of a mailing list."""
+import os
import sys
+import sha
+import base64
import datetime
import optparse
@@ -42,6 +45,7 @@ DOLLAR_STRINGS = ('msg_header', 'msg_footer',
'autoresponse_postings_text',
'autoresponse_admin_text',
'autoresponse_request_text')
+SALT_LENGTH = 4 # bytes
@@ -59,7 +63,8 @@ class Indenter:
assert self._indent >= 0
def write(self, s):
- self._fp.write(self._indent * self._width * ' ')
+ if s <> '\n':
+ self._fp.write(self._indent * self._width * ' ')
self._fp.write(s)
@@ -160,7 +165,7 @@ class XMLDumper(object):
else:
self._element('option', value, name=varname)
- def _dump_list(self, mlist, with_passwords):
+ def _dump_list(self, mlist, password_scheme):
# Write list configuration values
self._push_element('list', name=mlist._internal_name)
self._push_element('configuration')
@@ -185,8 +190,8 @@ class XMLDumper(object):
attrs['original'] = cased
self._push_element('member', **attrs)
self._element('realname', mlist.getMemberName(member))
- if with_passwords:
- self._element('password', mlist.getMemberPassword(member))
+ self._element('password',
+ password_scheme(mlist.getMemberPassword(member)))
self._element('language', mlist.getMemberLanguage(member))
# Delivery status, combined with the type of delivery
attrs = {}
@@ -230,7 +235,7 @@ class XMLDumper(object):
self._pop_element('roster')
self._pop_element('list')
- def dump(self, listnames, with_passwords=False):
+ def dump(self, listnames, password_scheme):
print >> self._fp, '<?xml version="1.0" encoding="UTF-8"?>'
self._push_element('mailman', **{
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
@@ -242,7 +247,7 @@ class XMLDumper(object):
except Errors.MMUnknownListError:
print >> sys.stderr, _('No such list: %(listname)s')
continue
- self._dump_list(mlist, with_passwords)
+ self._dump_list(mlist, password_scheme)
self._pop_element('mailman')
def close(self):
@@ -251,6 +256,41 @@ class XMLDumper(object):
+def no_password(password):
+ return '{NONE}'
+
+
+def plaintext_password(password):
+ return '{PLAIN}' + password
+
+
+def sha_password(password):
+ h = sha.new(password)
+ return '{SHA}' + base64.b64encode(h.digest())
+
+
+def ssha_password(password):
+ salt = os.urandom(SALT_LENGTH)
+ h = sha.new(password)
+ h.update(salt)
+ return '{SSHA}' + base64.b64encode(h.digest() + salt)
+
+
+SCHEMES = {
+ 'none' : no_password,
+ 'plain' : plaintext_password,
+ 'sha' : sha_password,
+ }
+
+try:
+ os.urandom(1)
+except NotImplementedError:
+ pass
+else:
+ SCHEMES['ssha'] = ssha_password
+
+
+
def parseargs():
parser = optparse.OptionParser(version=mm_cfg.VERSION,
usage=_("""\
@@ -262,10 +302,15 @@ Export the configuration and members of a mailing list in XML format."""))
help=_("""\
Output XML to FILENAME. If not given, or if FILENAME is '-', standard out is
used."""))
- parser.add_option('-p', '--with-passwords',
+ parser.add_option('-p', '--password-scheme',
+ default='none', type='string', help=_("""\
+Specify the RFC 2307 style hashing scheme for passwords included in the
+output. Use -P to get a list of supported schemes, which are
+case-insensitive."""))
+ parser.add_option('-P', '--list-hash-schemes',
default=False, action='store_true', help=_("""\
-With this option, user passwords are included in cleartext. For this reason,
-the default is to not include passwords."""))
+List the supported password hashing schemes and exit. The scheme labels are
+case-insensitive."""))
parser.add_option('-l', '--listname',
default=[], action='append', type='string',
metavar='LISTNAME', dest='listnames', help=_("""\
@@ -275,6 +320,12 @@ included in the XML output. Multiple -l flags may be given."""))
if args:
parser.print_help()
parser.error(_('Unexpected arguments'))
+ if opts.list_hash_schemes:
+ for label in SCHEMES:
+ print label.upper()
+ sys.exit(0)
+ if opts.password_scheme.lower() not in SCHEMES:
+ parser.error(_('Invalid password scheme'))
return parser, opts, args
@@ -293,7 +344,7 @@ def main():
listnames = opts.listnames
else:
listnames = Utils.list_names()
- dumper.dump(listnames, opts.with_passwords)
+ dumper.dump(listnames, SCHEMES[opts.password_scheme])
dumper.close()
finally:
if fp is not sys.stdout: